Make every HTTP call resilient in one line

Named clients from appsettings.json, retry with backoff, per-call timeout, rate limiting and call logging — on top of IHttpClientFactory. Timeouts and network errors come back as status codes (408 / 502 / 500), not exceptions. Targets net8.0, net9.0, net10.0.

$ dotnet add package CSharpEssentials.HttpHelper

Quick Start

1

Configure a named client

appsettings.json
{
  "HttpClientOptions": [
    { "Name": "github", "UseCompression": true }
  ]
}
2

Register it

Program.cs
builder.Services.AddHttpClients(builder.Configuration);
3

Call it

GitHubService.cs
public class GitHubService(IhttpsClientHelperFactory factory)
{
    public async Task<string> GetUserAsync(string user, CancellationToken ct)
    {
        var response = await factory.CreateOrGet("github")
            .addRetryCondition(r => (int)r.StatusCode >= 500, retryCount: 3, backoffFactor: 2)
            .addTimeout(TimeSpan.FromSeconds(10))
            .SendAsync($"https://api.github.com/users/{user}", HttpMethod.Get,
                       headers: new Dictionary<string, string> { ["User-Agent"] = "my-app" },
                       cancellationToken: ct);

        return await response.Content.ReadAsStringAsync(ct);
    }
}

What you get out of the box

FeatureHowBehaviour
Named clientsHttpClientOptions + AddHttpClients()Pooled handlers via IHttpClientFactory
Retry with backoffaddRetryCondition(predicate, count, factor)Wait before retry n = factorn seconds; adds X-Retry-Attempt
TimeoutaddTimeout(TimeSpan)Expiry returns 408
Rate limitingRateLimitOptions (sliding window)Over the limit returns 429
Transport errorsautomaticHttpRequestException returns 502, other exceptions 500
Caller cancellationCancellationTokenThrows OperationCanceledException (standard .NET)
Request callbacksAddRequestActionRequest, response, retry attempt, elapsed; failures are isolated
Compression, proxy, certificateconfigApplied to the pooled SocketsHttpHandler
MockingIHttpMockScenario in DIMatching requests never reach the network

JSON and the fluent API

POST a JSON body

JsonContentBuilder sends body.ToString(), so pass an already serialized string.

C#
var json = JsonSerializer.Serialize(new { name = "Alice", age = 30 });

var response = await factory.CreateOrGet("api")
    .SendAsync("https://api.example.com/users", HttpMethod.Post,
               new JsonContentBuilder(), json);

response.EnsureSuccessStatusCode();
var created = await response.Content.ReadFromJsonAsync<User>();

Chain headers, auth, retry and timeout

C#
var helper = factory.CreateOrGet("api")
    .setHeadersAndBearerAuthenticationSync(
        new Dictionary<string, string> { ["Accept"] = "application/json" },
        new httpsClientHelper.httpClientAuthenticationBearer(token))
    .addRetryCondition(r => r.StatusCode == HttpStatusCode.TooManyRequests || (int)r.StatusCode >= 500,
                       retryCount: 3, backoffFactor: 2)   // waits 2s, 4s, 8s
    .addTimeout(TimeSpan.FromSeconds(5));

var response = await helper.SendAsync("https://api.example.com/orders", HttpMethod.Get);

Rate limit and compression (config)

appsettings.json
{
  "HttpClientOptions": [
    {
      "Name": "api",
      "UseCompression": true,
      "RateLimitOptions": {
        "IsEnabled": true, "PermitLimit": 10, "Window": "00:00:01",
        "SegmentsPerWindow": 2, "QueueLimit": 0, "AutoReplenishment": true
      }
    }
  ]
}

HttpHelper vs Refit, Flurl and plain HttpClient

They solve different problems; this is what you get out of the box.

HttpHelperRefitFlurlPlain HttpClient
StyleNamed client + fluent helperTyped interfaceFluent URL builderManual
Retry / backoffBuilt in (Polly)Add Polly / resilience handlerAdd Polly or custom handlerAdd Polly / resilience handler
Timeout408 responseTaskCanceledExceptionFlurlHttpTimeoutExceptionTaskCanceledException
Transport errors502 / 500 responseExceptionExceptionException
Rate limitingBuilt in (config)——Add RateLimiter handler
JSON (de)serializationYou serializeAutomatic, typedAutomaticSystem.Net.Http.Json
Compile-time typed APINoYesNoNo
Mocking in testsMock scenarios via DIMock handlerHttpTestMock handler

Pick HttpHelper for a handful of third-party APIs where you want retry, timeout, rate limit and logging in one place. Pick Refit for a large, stable API you want typed. Pick Flurl for ad-hoc fluent calls with automatic JSON. They can coexist.

Try it

View on NuGet Star on GitHub