YamlHttpClient 2.0.0

dotnet add package YamlHttpClient --version 2.0.0
                    
NuGet\Install-Package YamlHttpClient -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="YamlHttpClient" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="YamlHttpClient" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="YamlHttpClient" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add YamlHttpClient --version 2.0.0
                    
#r "nuget: YamlHttpClient, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package YamlHttpClient@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=YamlHttpClient&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=YamlHttpClient&version=2.0.0
                    
Install as a Cake Tool

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

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 AutoCallAsync instead of BuildRequestMessage + SendAsync whenever you want retry and cache to work together correctly — the factory-based overload of SendAsync is 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.template is omitted or blank, ExecuteSetAsync returns 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 31 3/12/2026
2.0.0-rc5 34 3/12/2026
2.0.0-rc4 33 3/12/2026
2.0.0-rc3 36 3/12/2026
2.0.0-rc2 75 3/9/2026
2.0.0-rc1 85 3/9/2026
1.0.8 579 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
Loading failed