SimpleHttpClient 5.1.0

dotnet add package SimpleHttpClient --version 5.1.0
                    
NuGet\Install-Package SimpleHttpClient -Version 5.1.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="SimpleHttpClient" Version="5.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SimpleHttpClient" Version="5.1.0" />
                    
Directory.Packages.props
<PackageReference Include="SimpleHttpClient" />
                    
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 SimpleHttpClient --version 5.1.0
                    
#r "nuget: SimpleHttpClient, 5.1.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 SimpleHttpClient@5.1.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=SimpleHttpClient&version=5.1.0
                    
Install as a Cake Addin
#tool nuget:?package=SimpleHttpClient&version=5.1.0
                    
Install as a Cake Tool

SimpleHttpClient

CI

An easy-to-use .NET wrapper for HttpClient. No extension methods, included interfaces for easy unit test mocking, and straightforward properties for easier debugging (the response body is available as a string, byte array, and/or a typed object). It also supports streaming responses, with built-in helpers for reading line-delimited streams and Server-Sent Events.

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 GET request with a body isn't supported — its HttpClient is backed by HttpWebRequest, which disallows it — and SimpleClient surfaces this as a NotSupportedException with an explanatory message. This works fine on modern runtimes (net8.0+); if you need to target .NET Framework, send the body with a POST/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 HttpResponseMessage and connection are held open until you dispose the returned ISimpleStreamResponse. A using block is the simplest way to guarantee this.
  • MakeStreamRequest accepts a CancellationToken. 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 a StreamReader that gives you no place to pass it — so the await 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 an OperationCanceledException; a timeout still surfaces as a TimeoutException.
  • The body is yours to frame. SimpleStreamResponse.Body is a plain Stream. For common cases there are helpers (below) that read it as lines or Server-Sent Events; otherwise you can frame it however you like.

Reading lines and Server-Sent Events

ISimpleStreamResponse has two methods that cover the most common streaming formats. Both are IAsyncEnumerable<T>, so you consume them with await foreach, and both honor a CancellationToken:

// Line-delimited streams (NDJSON, plain text, etc.)
await foreach (var line in response.ReadLinesAsync(cancellationToken))
{
    Console.WriteLine(line);
}
// Server-Sent Events (text/event-stream)
await foreach (var sse in response.ReadServerSentEventsAsync(cancellationToken))
{
    // sse.Data, sse.EventType, sse.Id, sse.Retry
    Console.WriteLine(sse.Data);
}

ReadServerSentEventsAsync parses the SSE wire format per the WHATWG specification: events are separated by blank lines, multiple data: lines are joined with newlines, and comment/keep-alive lines (starting with :) are skipped. It deliberately does not handle application-specific conventions — most notably it does not treat any sentinel value specially and does not deserialize the payload — so those stay in your hands:

using var response = await client.MakeStreamRequest(request, cancellationToken);

await foreach (var sse in response.ReadServerSentEventsAsync(cancellationToken))
{
    if (sse.Data == "end") // a sentinel some APIs send to mark the end - your convention, not the library's
    {
        break;
    }

    var chunk = client.Serializer.Deserialize<MyChunk>(sse.Data);
    // ...handle chunk
}

This keeps SimpleHttpClient general-purpose: the SSE framing is a web standard (the same format EventSource consumes in browsers), while which sentinel terminates the stream and how each data payload is shaped are specific to the API you're calling.

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.

JSON serialization

The default JSON serializer (SimpleHttpDefaultJsonSerializer) is backed by System.Text.Json. It serializes with camelCase names, omits null values, writes indented output, and deserializes case-insensitively. For smoother interop it also reads numbers from JSON strings (e.g. "123") and tolerates trailing commas and comments while reading. The equivalent SimpleHttpSystemTextJsonSerializer is also available for callers who reference it explicitly.

Note: the defaults above cover the most common cases, but deserialization is strict in two ways they don't soften:

  • Non-public parameterless constructors aren't used — add a public constructor or a [JsonConstructor].
  • Wrong-shape values aren't coerced — a field that's sometimes a string and sometimes an object (and similar) will throw. For such fields, attach a custom JsonConverter to the property.

If you need different behavior wholesale, implement ISimpleHttpSerializer with your own serializer and set it on the client.

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 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. 
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.