Welcome to Kampute.HttpClient

Kampute.HttpClient is a lightweight .NET library for building REST API clients on top of the native HttpClient. It keeps the familiar .NET HTTP stack while adding the pieces most REST integrations need around it: reusable clients, request scopes, typed response deserialization, retry strategies, structured error handling, and request/response hooks.

Use it when you want a small client layer instead of a generated API SDK, or when you need direct control over HttpClient while still avoiding repeated boilerplate in every request.

Core Capabilities

HttpRestClient wraps HttpClient and focuses on common REST workflows:

The library does not hide HttpClient. You can provide your own instance, configure handlers and timeouts yourself, or let HttpRestClient use a shared client instance.

Quick Start

Install the base package and one serializer package for the content type you want to consume. For most APIs, start with the System.Text.Json package.

dotnet add package Kampute.HttpClient.Json

Create an HttpRestClient, configure accepted response formats, and send requests asynchronously.

using Kampute.HttpClient;
using Kampute.HttpClient.Json;

using var client = new HttpRestClient();

client.AcceptJson();

var data = await client.GetAsync<MyModel>("https://api.example.com/resource");

AcceptJson() registers the JSON deserializer and lets the client advertise JSON through the Accept header when the request does not already provide one.

Choosing Packages

The base package contains HttpRestClient, request helpers, scopes, retry strategies, error handlers, compression content wrappers, and the deserializer registry. Serializer packages are separate so applications only reference the serializers they use.

PackageUse it for
Kampute.HttpClientCore HTTP client, request helpers, scopes, retry behavior, and error handling.
Kampute.HttpClient.JsonJSON APIs using System.Text.Json.
Kampute.HttpClient.NewtonsoftJsonJSON APIs that require Newtonsoft.Json features or compatibility.
Kampute.HttpClient.XmlXML APIs using XmlSerializer.
Kampute.HttpClient.DataContractXML APIs using DataContractSerializer.

You can combine serializer packages when an API can return more than one content type.

using Kampute.HttpClient;
using Kampute.HttpClient.DataContract;
using Kampute.HttpClient.NewtonsoftJson;

using var client = new HttpRestClient();

client.AcceptJson();
client.AcceptXml();

var result = await client.GetAsync<MyResource>("https://api.example.com/resource");

Working With HttpClient

By default, HttpRestClient acquires a shared HttpClient instance. This avoids creating a new connection pool for every short-lived client wrapper.

using Kampute.HttpClient;

using var client = new HttpRestClient();

If your application already manages HttpClient instances, pass one in directly. This is useful when you configure handlers, proxies, default timeouts, or dependency-injection lifetimes elsewhere.

using Kampute.HttpClient;

var httpClient = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(30)
};

using var client = new HttpRestClient(httpClient);

Set BaseAddress when most requests target the same API. The client normalizes missing trailing slashes so relative paths resolve predictably.

using var client = new HttpRestClient
{
    BaseAddress = new Uri("https://api.example.com/v1")
};

var account = await client.GetAsync<Account>("accounts/current");

Sending Requests

The core package includes helpers for common request shapes:

Use content-specific packages for convenient request payload helpers such as PostAsJsonAsync(), PatchAsJsonAsync(), and PostAsXmlAsync().

using Kampute.HttpClient;
using Kampute.HttpClient.Json;

using var client = new HttpRestClient();

client.AcceptJson();

var created = await client.PostAsJsonAsync<MyResource>(
    "https://api.example.com/resources",
    new { name = "New resource" });

Scoped Requests

Request scopes let you apply headers or properties to a group of operations without changing the client defaults. This is useful when a few endpoints need a different Accept header, tenant identifier, correlation value, or authentication state.

using Kampute.HttpClient;

using var client = new HttpRestClient();

var csv = await client
    .WithScope()
    .SetHeader("Accept", MediaTypeNames.Text.Csv)
    .PerformAsync(scopedClient => scopedClient.GetAsStringAsync("https://api.example.com/report"));

You can also use explicit scopes when the same temporary configuration should apply to multiple requests.

using Kampute.HttpClient;

using var client = new HttpRestClient();

using (client.BeginHeaderScope(new Dictionary<string, string?>
{
    ["X-Tenant"] = "northwind"
}))
{
    var customer = await client.GetAsync<Customer>("https://api.example.com/customers/42");
    var orders = await client.GetAsync<Order[]>("https://api.example.com/customers/42/orders");
}

When the scope is disposed, the temporary headers and properties are removed.

Serializer Packages

The base package does not include a default content deserializer. Each serializer package registers a deserializer with ResponseDeserializers and exposes payload helpers for its content type.

You can also implement custom deserializers for application-specific content types.

using Kampute.HttpClient.Content.Abstracts;

public sealed class VendorContentDeserializer
    : HttpContentDeserializer
{
    public VendorContentDeserializer()
        : base("application/vnd.example.resource+json")
    {
    }

    public override Task<object?> DeserializeAsync(
        HttpContent content,
        Type modelType,
        CancellationToken cancellationToken = default)
    {
        // Deserialize the vendor-specific payload here.
        throw new NotImplementedException();
    }
}
using Kampute.HttpClient;

using var client = new HttpRestClient();

client.ResponseDeserializers.Add(new VendorContentDeserializer());

Retry Behavior

Retry strategies help clients recover from transient connection failures without duplicating retry loops around every request. Set BackoffStrategy to choose how long the client waits between attempts.

using Kampute.HttpClient;

using var client = new HttpRestClient();

client.BackoffStrategy = BackoffStrategies.Fibonacci(
    maxAttempts: 5,
    initialDelay: TimeSpan.FromSeconds(1));

Built-in strategies include:

Retry strategies can be combined with limits and jitter where appropriate for the API you are calling.

HTTP Error Handling

When a response status code indicates failure, the client raises an HttpResponseException unless an error handler recovers from the response. Use ResponseErrorType when the server returns structured error bodies, and register handlers when a status code needs custom recovery behavior.

using Kampute.HttpClient;
using Kampute.HttpClient.ErrorHandlers;

using var unauthorizedErrorHandler = new HttpError401Handler(async (client, challenges, cancellationToken) =>
{
    var auth = await client.PostAsFormAsync<AuthToken>("https://api.example.com/auth",
    [
        KeyValuePair.Create("client_id", MY_APP_ID),
        KeyValuePair.Create("client_secret", MY_APP_SECRET)
    ]);

    return new AuthenticationHeaderValue(AuthSchemes.Bearer, auth.Token);
});

using var client = new HttpRestClient();

client.ErrorHandlers.Add(unauthorizedErrorHandler);

The core package includes handlers for common retry and authentication scenarios, including HttpError401Handler, HttpError429Handler, HttpError503Handler, and TransientHttpErrorHandler.

Request And Response Events

Subscribe to BeforeSendingRequest and AfterReceivingResponse when you need logging, diagnostics, request enrichment, or response inspection around every operation.

using Kampute.HttpClient;

using var client = new HttpRestClient();

client.BeforeSendingRequest += (_, args) =>
{
    args.Request.Headers.TryAddWithoutValidation("X-Correlation-Id", Guid.NewGuid().ToString("N"));
};

client.AfterReceivingResponse += (_, args) =>
{
    Console.WriteLine($"{(int)args.Response.StatusCode} {args.Response.ReasonPhrase}");
};

Event handlers run around the actual HTTP operation, so keep them small and predictable.

Common Integration Shape

A typical API wrapper keeps one configured HttpRestClient and exposes domain-specific methods around it.

using Kampute.HttpClient;
using Kampute.HttpClient.Json;

public sealed class AccountApiClient : IDisposable
{
    private readonly HttpRestClient _client;

    public AccountApiClient(Uri baseAddress, string bearerToken)
    {
        _client = new HttpRestClient
        {
            BaseAddress = baseAddress
        };

        _client.AcceptJson();
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
    }

    public Task<Account?> GetCurrentAccountAsync(CancellationToken cancellationToken = default)
    {
        return _client.GetAsync<Account>("accounts/current", cancellationToken);
    }

    public Task<Account?> RenameAccountAsync(string name, CancellationToken cancellationToken = default)
    {
        return _client.PatchAsJsonAsync<Account>("accounts/current", new { name }, cancellationToken);
    }

    public void Dispose()
    {
        _client.Dispose();
    }
}

This keeps the rest of the application focused on business operations instead of repeated HTTP setup.