SimpleHttpClient 4.2.0
See the version list below for details.
dotnet add package SimpleHttpClient --version 4.2.0
NuGet\Install-Package SimpleHttpClient -Version 4.2.0
<PackageReference Include="SimpleHttpClient" Version="4.2.0" />
<PackageVersion Include="SimpleHttpClient" Version="4.2.0" />
<PackageReference Include="SimpleHttpClient" />
paket add SimpleHttpClient --version 4.2.0
#r "nuget: SimpleHttpClient, 4.2.0"
#:package SimpleHttpClient@4.2.0
#addin nuget:?package=SimpleHttpClient&version=4.2.0
#tool nuget:?package=SimpleHttpClient&version=4.2.0
SimpleHttpClient
An easy-to-use .NET wrapper for HttpClient. No extension methods, included interfaces allow for easy unit test mocking, and straightforward properties allow for easier debugging (the response body is available as a string, byte array, and/or a typed object).
Contents
Installation
SimpleHttpClient is available on NuGet and can installed through the NuGet Package Manager or by running
nuget install SimpleHttpClient
The package targets netstandard2.0 (for .NET Framework and older runtimes) and net8.0. On modern runtimes it uses SocketsHttpHandler with a pooled connection lifetime to keep DNS fresh; on netstandard2.0 it periodically rotates the underlying HttpClient to achieve the same.
Basic Usage
With Dependency Injection
SimpleHttpClient is designed to be used with dependency injection in order to avoid pitfalls that come with using an HttpClient:
In Program.cs:
// Register SimpleHttpClient with the ServiceCollection
await Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSimpleHttpClient();
})
.Build()
.RunAsync();
Then, inject ISimpleClientFactory and create a client with the host you want to call. This is the
preferred approach: each consumer gets its own client, so there's no shared, mutable Host to
collide over.
public class YourClientClass
{
private readonly ISimpleClient client;
// Retrieve an ISimpleClientFactory through dependency injection
public YourClientClass(ISimpleClientFactory clientFactory)
{
// Create a client for the host you'll be calling
client = clientFactory.CreateClient("https://api.sampleapis.com");
}
public async Task<string> MakeRequest()
{
// Pass the path you want to call into the SimpleRequest constructor
var request = new SimpleRequest("/coffee/hot");
// Call MakeRequest on the client, passing your request, and get your response back
var response = await client.MakeRequest(request);
return response.StringBody;
}
}
You can also inject ISimpleClient directly and set its Host (it's registered as transient, so
each consumer gets its own instance):
public YourClientClass(ISimpleClient client)
{
client.Host = "https://api.sampleapis.com";
}
Without Dependency Injection
If you're using SimpleHttpClient without dependency injection, you can just create an instance of SimpleClient:
public class YourClientClass
{
private readonly SimpleClient client;
public YourClientClass()
{
// Pass the host you'll be calling into the SimpleClient constructor
client = new SimpleClient("https://api.sampleapis.com");
}
public async Task<string> MakeRequest()
{
// Pass the path you want to call into the SimpleRequest constructor
var request = new SimpleRequest("/coffee/hot");
// Call MakeRequest on the client, passing your request, and get your response back
var response = await client.MakeRequest(request);
return response.StringBody;
}
}
NOTE: Although SimpleClient implements IDisposable, it should NOT be created inside a using block, but instead should be disposed with the class that uses it.
Typed Responses
You can also call MakeRequest with a type to deserialize the response body into that type:
public async Task<SomeResponseObject> MakeRequest()
{
// Pass the path you want to call into the SimpleRequest constructor
var request = new SimpleRequest("/get");
// Call MakeRequest<T> on the client, passing your request, and get your response back
var response = await client.MakeRequest<SomeResponseObject>(request);
return response.Body;
}
The untyped StringBody and ByteBody are still available on a typed response. If deserialization fails, response.Body will be null and the thrown exception is available on response.SerializationException.
Requests
A SimpleRequest defaults to a GET. Pass an HttpMethod to change it:
var request = new SimpleRequest("/post", HttpMethod.Post);
Query String Parameters
Query string parameters can be added directly to the path, via the QueryStringParameters dictionary, or both. Values in QueryStringParameters take precedence over duplicates in the path:
var request = new SimpleRequest("/get?param1=value1");
request.QueryStringParameters.Add("param2", "value2");
Form URL Encoded Parameters
Add application/x-www-form-urlencoded parameters via the FormUrlEncodedParameters dictionary. When present, these take precedence over any body set on the request:
var request = new SimpleRequest("/post", HttpMethod.Post);
request.FormUrlEncodedParameters.Add("param1", "value1");
request.FormUrlEncodedParameters.Add("param2", "value2");
Headers
Headers set on the request are merged with the client's DefaultHeaders (request headers win on conflicts):
// Sent with every request made by this client
client.DefaultHeaders["Authorization"] = "Bearer <token>";
// Sent with just this request
var request = new SimpleRequest("/get");
request.Headers["X-Custom-Header"] = "value";
Request Bodies
Pass an object as the body and it will be serialized using the client's serializer (JSON by default):
var request = new SimpleRequest("/post", HttpMethod.Post, new
{
param1 = "value1",
param2 = "value2",
});
Alternatively, set request.StringBody to send a pre-serialized string body. You can control the content type and encoding via request.ContentType and request.ContentEncoding.
After a request is sent, the request reflects what was actually sent: for an object Body, request.StringBody holds the serialized payload, and request.ContentType holds the resolved content type — handy for logging and debugging. An object Body is the source of truth and is re-serialized on every send (so changing Body and re-sending the same request sends the new value); a string body is sent as-is.
Note: On .NET Framework, sending a
GETrequest with a body isn't supported — itsHttpClientis backed byHttpWebRequest, which disallows it — and SimpleClient surfaces this as aNotSupportedExceptionwith an explanatory message. This works fine on modern runtimes (net8.0+); if you need to target .NET Framework, send the body with aPOST/PUT/etc. instead.
Streaming Responses
For responses you want to consume as they arrive — for example Server-Sent Events (SSE) or large downloads — use MakeStreamRequest. Unlike MakeRequest, it does not buffer the body into memory; it returns the live network stream as soon as the response headers are available.
var request = new SimpleRequest("/stream");
// The response holds the connection open, so dispose it when you're done (a using block is ideal).
using var response = await client.MakeStreamRequest(request);
if (!response.IsSuccessful)
{
// response.StatusCode and response.Headers are available immediately
}
// response.Body is the raw, unbuffered network stream
using var reader = new StreamReader(response.Body);
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
// Process each line as it arrives
Console.WriteLine(line);
}
A few things to keep in mind:
- Dispose the response. The underlying
HttpResponseMessageand connection are held open until you dispose the returnedISimpleStreamResponse. Ausingblock is the simplest way to guarantee this. MakeStreamRequestaccepts aCancellationToken. Pass one to cancel sending the request, waiting for the headers, and reading the stream (e.g. when a user aborts mid-stream). The token is observed by reads too, even through aStreamReaderthat gives you no place to pass it — so theawait reader.ReadLineAsync()loop above stops promptly when the token fires. Async reads honor it even mid-read; synchronous reads observe it between reads, so to abort a synchronous read already blocked on the socket, dispose the response. A caller-requested cancellation surfaces as anOperationCanceledException; a timeout still surfaces as aTimeoutException.- The body is yours to frame.
SimpleStreamResponse.Bodyis a plainStream, leaving any protocol-specific framing (such as SSEevent:/data:parsing) to you.
Configuration
Timeouts
The client Timeout defaults to 30 seconds and can be overridden per-request. Set the value to -1 to disable the timeout. A request that exceeds its timeout throws a TimeoutException:
client.Timeout = 60; // 60 seconds for all requests on this client
var request = new SimpleRequest("/slow");
request.TimeoutOverride = 120; // 120 seconds for just this request
For streaming requests, the timeout applies to receiving the response headers — not to how long you spend reading the stream.
Additional Successful Status Codes
By default, IsSuccessful is true for any 2xx status code. You can mark additional status codes as successful on the client (applies to all requests) and/or per-request:
client.AdditionalSuccessfulStatusCodes.Add(HttpStatusCode.NotFound);
var request = new SimpleRequest("/get");
request.AdditionalSuccessfulStatusCodes.Add(HttpStatusCode.NotAcceptable);
Custom Serializers
SimpleHttpClient ships with JSON (default) and XML serializers, and uses the serializer to both serialize request bodies and deserialize typed responses. Set one on the client, or override it per-request:
client.Serializer = new SimpleHttpDefaultXmlSerializer();
var request = new SimpleRequest("/get");
request.SerializerOverride = new SimpleHttpDefaultJsonSerializer();
You can supply your own serializer by implementing ISimpleHttpSerializer.
System.Text.Json
The default JSON serializer uses Newtonsoft.Json. A System.Text.Json-based serializer is also included and can be opted into the same way:
client.Serializer = new SimpleHttpSystemTextJsonSerializer();
Its settings mirror the default (camelCase names, null values omitted, indented output, case-insensitive deserialization), so it's a drop-in for most payloads. Note that System.Text.Json is stricter than Newtonsoft.Json — most notably it can't use a non-public parameterless constructor when deserializing, so such types need a public constructor or a [JsonConstructor].
Heads up:
SimpleHttpSystemTextJsonSerializeris slated to become the default in the next major version (v5), at which point theNewtonsoft.Jsondependency will be removed.
Logging
You can log requests and responses by setting the LogRequest and LogResponse delegates (called immediately before a request is sent and immediately after a response is received), or by providing an ISimpleHttpLogger:
client.LogRequest = (url, request) => Console.WriteLine($"--> {request.Method} {url}");
client.LogResponse = (response) => Console.WriteLine($"<-- {response.StatusCode}");
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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 was computed. 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 was computed. 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 | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.Http (>= 8.0.1)
- Newtonsoft.Json (>= 13.0.3)
- System.Text.Json (>= 8.0.5)
-
net8.0
- Microsoft.Extensions.Http (>= 8.0.1)
- Newtonsoft.Json (>= 13.0.3)
- System.Text.Json (>= 8.0.5)
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 | |
|---|---|---|---|
| 5.1.0 | 50 | 5/31/2026 | |
| 5.0.0 | 57 | 5/30/2026 | |
| 4.2.0 | 71 | 5/30/2026 | |
| 4.1.0 | 3,674 | 4/18/2024 | |
| 4.0.0 | 292 | 4/25/2023 | |
| 3.0.0 | 267 | 4/23/2023 | |
| 2.1.1 | 805 | 4/17/2023 | |
| 2.1.0 | 262 | 4/17/2023 | |
| 2.0.0 | 294 | 4/16/2023 | |
| 1.1.0 | 283 | 4/16/2023 | |
| 1.0.10 | 359 | 2/28/2023 | |
| 1.0.9 | 350 | 2/17/2023 | |
| 1.0.8 | 343 | 2/17/2023 | |
| 1.0.7 | 333 | 2/17/2023 | |
| 1.0.6 | 341 | 2/17/2023 | |
| 1.0.5 | 343 | 2/17/2023 | |
| 1.0.4 | 350 | 2/17/2023 | |
| 1.0.3 | 349 | 2/17/2023 | |
| 1.0.2 | 344 | 2/16/2023 | |
| 1.0.1 | 338 | 2/16/2023 |