SergeiM.Http 0.6.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package SergeiM.Http --version 0.6.0
                    
NuGet\Install-Package SergeiM.Http -Version 0.6.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="SergeiM.Http" Version="0.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SergeiM.Http" Version="0.6.0" />
                    
Directory.Packages.props
<PackageReference Include="SergeiM.Http" />
                    
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 SergeiM.Http --version 0.6.0
                    
#r "nuget: SergeiM.Http, 0.6.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 SergeiM.Http@0.6.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=SergeiM.Http&version=0.6.0
                    
Install as a Cake Addin
#tool nuget:?package=SergeiM.Http&version=0.6.0
                    
Install as a Cake Tool

SergeiM.Http

GitHub Actions Workflow Status NuGet Version Hits-of-Code GitHub License

A fluent HTTP client library for .NET that provides a pure OOP approach to building and executing HTTP requests with method chaining.

Installation

dotnet add package SergeiM.Http

Quick Start

Simple GET Request

var response = await new BaseRequest("https://api.example.com")
    .Uri().Path("/users").QueryParam("id", 123).Back()
    .Method(BaseRequest.GET)
    .FetchAsync();

JSON Response

var response = await new BaseRequest("https://api.example.com")
    .Uri().Path("/users").QueryParam("id", 123).Back()
    .Header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
    .FetchAsync();

var json = response.As<JsonResponse>().AssertStatus(200);
var user = json.AsObject();
string userName = user.GetString("name");
int age = user.GetInt("age");
bool isActive = user.GetBoolean("is_active", false);

XML Response with XPath

var response = await new BaseRequest("https://api.example.com")
    .Uri().Path("/data").Back()
    .Header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)
    .FetchAsync();

string href = response.As<XmlResponse>()
    .AssertStatus(200)
    .AssertXPath("/page/links/link[@rel='see']")
    .EvaluateXPath("/page/links/link[@rel='see']/@href");

Complex Chaining Example

var firstResponse = await new BaseRequest("https://www.example.com:8080")
    .Uri().Path("/users").QueryParam("id", 333).Back()
    .Method(BaseRequest.GET)
    .Header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)
    .FetchAsync();

var secondResponse = await firstResponse
    .As<XmlResponse>()
    .AssertStatus(200)
    .AssertXPath("/page/links/link[@rel='see']")
    .Rel("/page/links/link[@rel='see']/@href")
    .Header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
    .FetchAsync();

Synchronous Methods (Blocks Threads)

The library also exposes synchronous wrappers for backward compatibility and scripting scenarios. These methods call their async counterparts via .GetAwaiter().GetResult(), which blocks the calling thread and can lead to deadlocks in UI or ASP.NET contexts.

Sync method / property Async alternative Blocks on
IRequest.Fetch() IRequest.FetchAsync() FetchAsync()
IWire.Send() IWire.SendAsync() SendAsync()
IResponse.Content — (always blocking) ReadAsStringAsync()

Prefer FetchAsync() and SendAsync() in all new code.

var response = new BaseRequest("https://api.example.com")
    .Uri().Path("/users").QueryParam("id", 123).Back()
    .Method(BaseRequest.GET)
    .Fetch();

HttpClient Lifecycle

The default new HttpWire() constructor creates new HttpClient() and holds it for the wire's lifetime. This is fine for short-lived apps but causes socket exhaustion and stale DNS in long-running server applications.

Console apps / one-shot scripts

For short-lived processes, a single HttpClient instance is all you need:

using var client = new HttpClient { BaseAddress = new Uri("https://api.example.com") };
var wire = new HttpWire(client);
var user = await new BaseRequest("/users/1", wire).FetchAsync();

ASP.NET Core / long-running services

Use IHttpClientFactory via ManagedHttpWire — it creates and disposes an HttpClient per request, letting the factory manage handler pooling and DNS rotation:

// Program.cs
builder.Services.AddHttpClient();
builder.Services.AddSingleton<IWire>(sp =>
    new ManagedHttpWire(() => sp.GetRequiredService<IHttpClientFactory>().CreateClient()));

With a named client for custom settings:

builder.Services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com");
    c.DefaultRequestHeaders.Add("User-Agent", "MyApp");
});
builder.Services.AddSingleton<IWire>(sp =>
    new ManagedHttpWire(() => sp.GetRequiredService<IHttpClientFactory>().CreateClient("github")));

ManagedHttpWire is stateless and can be registered as a Singleton.

Warning: Avoid new HttpWire() (parameterless) in server applications. It creates an unmanaged HttpClient that will exhaust sockets over time.

Wire System

SergeiM.Http uses a wire system for sending HTTP requests, allowing you to customize and extend request handling through decorators.

Basic Wire Usage

By default, requests use HttpWire internally. For long-running applications, prefer ManagedHttpWire with IHttpClientFactory (see HttpClient Lifecycle).

var response = await new BaseRequest("https://api.example.com").FetchAsync();

Custom Wire

You can specify a custom wire implementation:

var response = await new BaseRequest("https://api.example.com",
    new ManagedHttpWire(() => new HttpClient())).FetchAsync();

Changing Wire with Through()

Change the wire at any point using the Through() method:

var response = await new BaseRequest("https://api.example.com")
    .Through(new ManagedHttpWire(() => new HttpClient()))
    .FetchAsync();

Authorization Wire

Add authorization headers to requests:

var response = await new BaseRequest("https://api.example.com")
    .Through(new BasicAuthWire(
        new ManagedHttpWire(() => new HttpClient()),
        "Bearer your-token"))
    .FetchAsync();

Retry Wire

Add automatic retry logic for failed requests:

var response = await new BaseRequest("https://api.example.com", new RetryWire(
    new ManagedHttpWire(() => new HttpClient()),
    maxRetries: 5,
    delayBetweenRetries: TimeSpan.FromSeconds(2)
)).FetchAsync();

Chaining Wire Decorators

Combine multiple decorators for advanced functionality:

var wire = new RetryWire(
    new BasicAuthWire(
        new ManagedHttpWire(() => new HttpClient()),
        "Bearer token"
    ),
    maxRetries: 3,
    delayBetweenRetries: TimeSpan.FromSeconds(1)
);
var response = await new BaseRequest("https://api.example.com", wire)
    .Uri().Path("/data").Back()
    .FetchAsync();

Or fluently with Through():

var response = await new BaseRequest("https://api.example.com")
    .Through(new BasicAuthWire(
        new ManagedHttpWire(() => new HttpClient()),
        "Bearer token"))
    .Uri().Path("/protected-resource").Back()
    .Through(new RetryWire(
        new BasicAuthWire(
            new ManagedHttpWire(() => new HttpClient()),
            "Bearer token"),
        3,
        TimeSpan.FromSeconds(2)
    ))
    .FetchAsync();

Custom Wire Implementation

Create your own wire by implementing IWire. Always implement SendAsync() as the primary method. The synchronous Send() method is provided for backward compatibility — it blocks the calling thread and should be avoided in new code.

public class LoggingWire : IWire
{
    private readonly IWire _origin;
    private readonly ILogger _logger;

    public LoggingWire(IWire origin, ILogger logger)
    {
        _origin = origin;
        _logger = logger;
    }

    public async Task<HttpResponseMessage> SendAsync(
        string method,
        string uri,
        Dictionary<string, string> headers,
        string? body = null)
    {
        _logger.Log($"Sending {method} request to {uri}");
        var response = await _origin.SendAsync(method, uri, headers, body);
        _logger.Log($"Received {response.StatusCode} from {uri}");
        return response;
    }

    // Provided for sync consumers only — blocks the calling thread.
    // Prefer SendAsync() in new code.
    public HttpResponseMessage Send(
        string method,
        string uri,
        Dictionary<string, string> headers,
        string? body = null)
    {
        return SendAsync(method, uri, headers, body).GetAwaiter().GetResult();
    }
}

Building

dotnet build

Testing

dotnet test

Conventional Commits

This project follows Conventional Commits to automate versioning and changelog generation via release-please.

Type Purpose Bump
feat New feature minor
fix Bug fix patch
docs Documentation only changes
style Code style (formatting, whitespace)
refactor Code refactoring
test Adding or updating tests
chore Maintenance (CI, deps, etc.)

Breaking changes are signaled with ! after the type (feat!:) or a BREAKING CHANGE: footer — triggers a major bump.

License

See LICENSE.txt for details.

Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on SergeiM.Http:

Package Downloads
SergeiM.Soap

Immutable fluent SOAP 1.1 and SOAP 1.2 client for .NET 8, built on SergeiM.Http. Send SOAP requests, parse envelopes and faults, and assert responses with a chainable API.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.6.1 89 7/21/2026
0.6.0 97 7/21/2026
0.5.0 108 6/24/2026
0.4.1 223 2/25/2026
0.4.0 132 2/4/2026
0.3.0 121 2/3/2026
0.2.0 131 1/15/2026
0.1.1 125 12/28/2025
0.1.0 172 12/20/2025
0.0.1 181 12/14/2025