SergeiM.Http
0.6.1
dotnet add package SergeiM.Http --version 0.6.1
NuGet\Install-Package SergeiM.Http -Version 0.6.1
<PackageReference Include="SergeiM.Http" Version="0.6.1" />
<PackageVersion Include="SergeiM.Http" Version="0.6.1" />
<PackageReference Include="SergeiM.Http" />
paket add SergeiM.Http --version 0.6.1
#r "nuget: SergeiM.Http, 0.6.1"
#:package SergeiM.Http@0.6.1
#addin nuget:?package=SergeiM.Http&version=0.6.1
#tool nuget:?package=SergeiM.Http&version=0.6.1
SergeiM.Http
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.
Synchronous usage (not recommended)
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 unmanagedHttpClientthat 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 | Versions 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. |
-
net10.0
- SergeiM.Json (>= 0.2.0)
-
net8.0
- SergeiM.Json (>= 0.2.0)
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.
See release notes at: https://github.com/svmukhin/sergeim-http/releases/tag/v0.6.1