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:
- Send common HTTP methods through concise async helpers.
- Deserialize successful responses into typed .NET objects.
- Read raw response bodies as strings, streams, or byte arrays when needed.
- Register JSON, XML, or custom response deserializers.
- Apply headers and request properties globally or inside temporary scopes.
- Configure retry behavior for transient connection failures.
- Handle HTTP error responses with reusable handlers.
- Inspect outgoing requests and incoming responses through lifecycle events.
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.
| Package | Use it for |
|---|---|
Kampute.HttpClient | Core HTTP client, request helpers, scopes, retry behavior, and error handling. |
Kampute.HttpClient.Json | JSON APIs using System.Text.Json. |
Kampute.HttpClient.NewtonsoftJson | JSON APIs that require Newtonsoft.Json features or compatibility. |
Kampute.HttpClient.Xml | XML APIs using XmlSerializer. |
Kampute.HttpClient.DataContract | XML 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:
GetAsync<T>(),PostAsync<T>(),PutAsync<T>(),PatchAsync<T>(), andDeleteAsync<T>()for typed responses.GetAsStringAsync(),GetAsByteArrayAsync(), andGetAsStreamAsync()for raw response bodies.HeadAsync()andOptionsAsync()for response headers.SendAsync()for lower-level control over the HTTP method and payload.
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.
Kampute.HttpClient.Json: JSON support throughSystem.Text.Json.Kampute.HttpClient.NewtonsoftJson: JSON support throughNewtonsoft.Json.Kampute.HttpClient.Xml: XML support throughXmlSerializer.Kampute.HttpClient.DataContract: XML support throughDataContractSerializer.
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:
BackoffStrategies.Nonefor no retry delay.BackoffStrategies.Once()for a single retry after a delay.BackoffStrategies.Uniform()for a fixed delay.BackoffStrategies.Linear()for linearly increasing delays.BackoffStrategies.Exponential()for exponential backoff.BackoffStrategies.Fibonacci()for gradually increasing delays.
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.
