Fuzn.FluentHttp
0.0.7-beta
See the version list below for details.
dotnet add package Fuzn.FluentHttp --version 0.0.7-beta
NuGet\Install-Package Fuzn.FluentHttp -Version 0.0.7-beta
<PackageReference Include="Fuzn.FluentHttp" Version="0.0.7-beta" />
<PackageVersion Include="Fuzn.FluentHttp" Version="0.0.7-beta" />
<PackageReference Include="Fuzn.FluentHttp" />
paket add Fuzn.FluentHttp --version 0.0.7-beta
#r "nuget: Fuzn.FluentHttp, 0.0.7-beta"
#:package Fuzn.FluentHttp@0.0.7-beta
#addin nuget:?package=Fuzn.FluentHttp&version=0.0.7-beta&prerelease
#tool nuget:?package=Fuzn.FluentHttp&version=0.0.7-beta&prerelease
Fuzn.FluentHttp
A lightweight fluent API for building and sending HTTP requests with HttpClient. Provides a clean, chainable interface for configuring URLs, headers, content types, authentication, and serialization.
Installation
dotnet add package Fuzn.FluentHttp
Quick Start
using Fuzn.FluentHttp;
var httpClient = new HttpClient();
// Simple GET request with typed response
var response = await httpClient
.Url("https://api.example.com/users/1")
.Get<User>();
if (response.IsSuccessful)
{
Console.WriteLine(response.Data!.Name);
}
Features
Typed Responses
Use generic HTTP methods to get strongly-typed responses:
var response = await httpClient.Url("/api/users/1").Get<User>();
if (response.IsSuccessful)
{
User user = response.Data!;
}
else
{
Console.WriteLine($"Error {response.StatusCode}: {response.Content}");
}
HTTP Methods
All standard HTTP methods are supported, with both generic and non-generic versions:
// Non-generic (returns HttpResponse)
await httpClient.Url("/api/resource").Get();
await httpClient.Url("/api/resource").Post();
await httpClient.Url("/api/resource").Put();
await httpClient.Url("/api/resource").Patch();
await httpClient.Url("/api/resource").Delete();
await httpClient.Url("/api/resource").Head();
await httpClient.Url("/api/resource").Options();
// Generic (returns HttpResponse<T>)
await httpClient.Url("/api/resource").Get<MyType>();
await httpClient.Url("/api/resource").Post<MyType>();
await httpClient.Url("/api/resource").Put<MyType>();
await httpClient.Url("/api/resource").Patch<MyType>();
await httpClient.Url("/api/resource").Delete<MyType>();
Custom HTTP Methods
For non-standard HTTP methods (e.g., WebDAV's PROPFIND, MKCOL), use the Send method:
// Custom HTTP method
var response = await httpClient
.Url("https://webdav.example.com/folder")
.Send(new HttpMethod("PROPFIND"));
// With typed response
var response = await httpClient
.Url("https://webdav.example.com/folder")
.WithContent(propfindRequest)
.Send<PropfindResponse>(new HttpMethod("MKCOL"));
// With streaming response
await using var streamResponse = await httpClient
.Url("https://webdav.example.com/largefile")
.SendStream(new HttpMethod("PROPFIND"));
Request Content
var response = await httpClient
.Url("https://api.example.com/users")
.WithContent(new { Name = "John", Email = "john@example.com" })
.Post<User>();
Query Parameters
// Single parameter
var response = await httpClient
.Url("https://api.example.com/search")
.WithQueryParam("q", "dotnet")
.WithQueryParam("page", 1)
.Get<SearchResult>();
// Multiple parameters via dictionary
var response = await httpClient
.Url("https://api.example.com/search")
.WithQueryParams(new Dictionary<string, object?>
{
["q"] = "dotnet",
["page"] = 1
})
.Get<SearchResult>();
// Anonymous object
var response = await httpClient
.Url("https://api.example.com/search")
.WithQueryParams(new { q = "dotnet", page = 1 })
.Get<SearchResult>();
// Multiple values for same parameter
var response = await httpClient
.Url("https://api.example.com/items")
.WithQueryParam("tags", new[] { "c#", "dotnet", "http" })
.Get<ItemList>();
Headers
var response = await httpClient
.Url("https://api.example.com/data")
.WithHeader("X-Custom-Header", "value")
.WithHeaders(new Dictionary<string, string>
{
["X-Another"] = "another-value"
})
.Get<Data>();
Authentication
// Bearer token
var response = await httpClient
.Url("https://api.example.com/protected")
.WithAuthBearer("your-jwt-token")
.Get<ProtectedData>();
// Basic authentication
var response = await httpClient
.Url("https://api.example.com/protected")
.WithAuthBasic("username", "password")
.Get<ProtectedData>();
// API Key
var response = await httpClient
.Url("https://api.example.com/protected")
.WithAuthApiKey("your-api-key")
.Get<ProtectedData>();
// Custom header name for API key
var response = await httpClient
.Url("https://api.example.com/protected")
.WithAuthApiKey("your-api-key", "Authorization")
.Get<ProtectedData>();
Content Types
var response = await httpClient
.Url("https://api.example.com/data")
.WithContentType(ContentTypes.Json)
.WithContent(data)
.Post<Result>();
// Custom content type
var response = await httpClient
.Url("https://api.example.com/graphql")
.WithContentType("application/graphql")
.WithContent(query)
.Post<GraphQLResponse>();
Accept Headers
var response = await httpClient
.Url("https://api.example.com/data")
.WithAccept(AcceptTypes.Json)
.Get<Data>();
// Custom accept type
var response = await httpClient
.Url("https://api.example.com/report")
.WithAccept("application/pdf")
.Get();
File Uploads
// Upload file from stream
var response = await httpClient
.Url("https://api.example.com/upload")
.WithFile("file", "document.pdf", fileStream, "application/pdf")
.Post<UploadResult>();
// Upload file from byte array
var response = await httpClient
.Url("https://api.example.com/upload")
.WithFile("file", "image.png", imageBytes, "image/png")
.Post<UploadResult>();
// Multiple files with form fields
var response = await httpClient
.Url("https://api.example.com/upload")
.WithFile("file1", "doc1.pdf", stream1)
.WithFile("file2", "doc2.pdf", stream2)
.WithFormField("description", "My documents")
.Post<UploadResult>();
Cookies
var response = await httpClient
.Url("https://api.example.com/data")
.WithCookie("session", "abc123")
.WithCookie("preference", "dark-mode", path: "/", duration: TimeSpan.FromDays(30))
.Get<Data>();
Timeouts
var response = await httpClient
.Url("https://api.example.com/slow-endpoint")
.WithTimeout(TimeSpan.FromSeconds(30))
.Get<Data>();
Custom User-Agent
var response = await httpClient
.Url("https://api.example.com/data")
.WithUserAgent("MyApp/1.0")
.Get<Data>();
HTTP Version
Control the HTTP protocol version for the request. Useful for HTTP/2, HTTP/3, or legacy system compatibility:
using System.Net;
// Force HTTP/2
var response = await httpClient
.Url("https://api.example.com/data")
.WithVersion(HttpVersion.Version20)
.Get<Data>();
// Force HTTP/3 (QUIC)
var response = await httpClient
.Url("https://api.example.com/data")
.WithVersion(HttpVersion.Version30)
.Get<Data>();
// HTTP/1.1 for legacy systems
var response = await httpClient
.Url("https://legacy-api.example.com/data")
.WithVersion(HttpVersion.Version11)
.Get<Data>();
HTTP Version Policy
Control how the HTTP version is negotiated with the server:
// Require exact version match (fail if server doesn't support it)
var response = await httpClient
.Url("https://api.example.com/data")
.WithVersion(HttpVersion.Version20)
.WithVersionPolicy(HttpVersionPolicy.RequestVersionExact)
.Get<Data>();
// Allow downgrade to lower versions
var response = await httpClient
.Url("https://api.example.com/data")
.WithVersion(HttpVersion.Version20)
.WithVersionPolicy(HttpVersionPolicy.RequestVersionOrLower)
.Get<Data>();
// Allow upgrade to higher versions
var response = await httpClient
.Url("https://api.example.com/data")
.WithVersion(HttpVersion.Version11)
.WithVersionPolicy(HttpVersionPolicy.RequestVersionOrHigher)
.Get<Data>();
Working with Responses
HttpResponse
var response = await httpClient
.Url("https://api.example.com/users/1")
.Get();
// Check if successful (2xx status code)
if (response.IsSuccessful)
{
// Deserialize to type
var user = response.ContentAs<User>();
// Get content as string
string content = response.Content;
}
// Access status code and reason
HttpStatusCode status = response.StatusCode;
string? reason = response.ReasonPhrase;
// Access content metadata
string? contentType = response.ContentType;
long? contentLength = response.ContentLength;
// Access HTTP version
Version version = response.Version;
// Access headers
var headers = response.Headers;
var contentHeaders = response.ContentHeaders;
// Access cookies
var cookies = response.Cookies;
// Access the request URI (useful after redirects)
Uri? requestUri = response.RequestUri;
HttpResponse<T>
var response = await httpClient
.Url("https://api.example.com/users/1")
.Get<User>();
// Typed data (auto-deserialized)
User? user = response.Data;
// Check success
bool success = response.IsSuccessful;
// Status code
HttpStatusCode status = response.StatusCode;
// Content as string
string content = response.Content;
// Headers and cookies
var headers = response.Headers;
var cookies = response.Cookies;
// Deserialize to a different type
var error = response.ContentAs<ProblemDetails>();
Debugging
ToString() for Request Inspection
The builder overrides ToString() to provide a formatted view of the current request configuration. This is useful for logging and debugging:
var builder = httpClient
.Url("https://api.example.com/users")
.WithContent(new { Name = "John" })
.WithAuthBearer("token123");
// Log the request
Console.WriteLine(builder);
// Output:
// === FluentHttp Request ===
// Method: (not set)
// URL: https://api.example.com/users
// Headers:
// Authorization: [REDACTED]
// Content-Type: application/json
// Accept: application/json
// Content: {"Name":"John"}
In Visual Studio, you can also hover over the builder variable in the debugger to see this information.
BuildRequest() for Advanced Inspection
To get the actual HttpRequestMessage that would be sent (after the BeforeSend interceptor runs):
var builder = httpClient
.Url("https://api.example.com/users")
.WithContent(new { Name = "John" });
// Get the HttpRequestMessage without sending
var request = builder.BuildRequest(HttpMethod.Post);
// Inspect the actual request
Console.WriteLine($"URL: {request.RequestUri}");
Console.WriteLine($"Content: {await request.Content!.ReadAsStringAsync()}");
// You can still send the request afterwards
var response = await builder.Post();
Custom Serialization
Using System.Text.Json Options
Configure the default System.Text.Json serializer with custom options:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
var response = await httpClient
.Url("https://api.example.com/data")
.WithJsonOptions(options)
.WithContent(data)
.Post<Result>();
Using a Custom Serializer Provider
Implement ISerializerProvider to use your preferred serializer:
public class NewtonsoftSerializerProvider : ISerializerProvider
{
public string Serialize<T>(T obj) =>
JsonConvert.SerializeObject(obj);
public T? Deserialize<T>(string json) =>
JsonConvert.DeserializeObject<T>(json);
}
// Use custom serializer
var response = await httpClient
.Url("https://api.example.com/data")
.WithSerializer(new NewtonsoftSerializerProvider())
.WithContent(data)
.Post<Result>();
Note: When both
WithJsonOptionsandWithSerializerare set,WithSerializertakes precedence andWithJsonOptionsis ignored.
Global Defaults
Use FluentHttpDefaults.BeforeSend to configure global behavior for all requests. The interceptor receives the builder, allowing you to inspect current request state via builder.Data and modify using builder methods.
Setting Global Serializer Options
var globalOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
FluentHttpDefaults.BeforeSend = builder =>
{
// Only set if not already configured per-request
if (builder.Data.SerializerOptions is null)
{
builder.WithJsonOptions(globalOptions);
}
};
Adding Default Headers
FluentHttpDefaults.BeforeSend = builder =>
{
// Add correlation ID to all requests
if (!builder.Data.Headers.ContainsKey("X-Correlation-Id"))
{
builder.WithHeader("X-Correlation-Id", Guid.NewGuid().ToString());
}
// Add app version header
builder.Data.Headers.TryAdd("X-App-Version", "1.0.0");
};
URL-Based Conditional Logic
FluentHttpDefaults.BeforeSend = builder =>
{
// Different serializer for legacy API
if (builder.Data.AbsoluteUri.Host.Contains("legacy-api"))
{
builder.WithSerializer(new LegacySerializerProvider());
}
// Longer timeout for report endpoints
if (builder.Data.RequestUrl.Contains("/reports/"))
{
builder.WithTimeout(TimeSpan.FromMinutes(5));
}
};
Clearing the Interceptor
FluentHttpDefaults.BeforeSend = null;
Note: Per-request settings always take precedence. The pattern is to check
builder.Datafirst, then only set defaults if not already configured. For async operations like token refresh, use aDelegatingHandlerinstead.
Resilience and Retry Policies
FluentHttp works seamlessly with resilience libraries like Polly through HttpClient's DelegatingHandler pipeline. This is the recommended approach for implementing retry policies, circuit breakers, and other resilience patterns.
Using Microsoft.Extensions.Http.Resilience (Recommended)
// Install: dotnet add package Microsoft.Extensions.Http.Resilience
services.AddHttpClient("MyApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddStandardResilienceHandler(); // Adds retry, circuit breaker, and timeout policies
Using Polly Directly
// Install: dotnet add package Microsoft.Extensions.Http.Polly
services.AddHttpClient("MyApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddTransientHttpErrorPolicy(builder =>
builder.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
Custom DelegatingHandler
public class RetryHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = null!;
for (int i = 0; i < 3; i++)
{
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
return response;
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)), cancellationToken);
}
return response;
}
}
// Register
services.AddHttpClient("MyApi")
.AddHttpMessageHandler<RetryHandler>();
Then use FluentHttp with the configured client:
var client = httpClientFactory.CreateClient("MyApi");
var response = await client
.Url("/api/users")
.Get<User[]>();
Cancellation Support
All HTTP methods support cancellation tokens:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var response = await httpClient
.Url("https://api.example.com/data")
.Get<Data>(cts.Token);
You can also set a cancellation token on the builder, which will be linked with any token passed to the HTTP method:
var builderCts = new CancellationTokenSource();
var methodCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
// Both tokens are linked - cancelling either will cancel the request
var response = await httpClient
.Url("https://api.example.com/data")
.WithCancellationToken(builderCts.Token)
.Get<Data>(methodCts.Token);
License
MIT License - see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Fuzn.FluentHttp:
| Package | Downloads |
|---|---|
|
Fuzn.TestFuzn.Plugins.Http
Http plugin for TestFuzn |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.6.8-beta | 385 | 2/13/2026 |
| 0.6.7-beta | 51 | 2/12/2026 |
| 0.6.6-beta | 55 | 2/9/2026 |
| 0.6.5-beta | 49 | 2/9/2026 |
| 0.6.4-beta | 52 | 2/8/2026 |
| 0.6.3-beta | 48 | 2/4/2026 |
| 0.6.2-beta | 47 | 2/4/2026 |
| 0.6.1-beta | 46 | 2/3/2026 |
| 0.6.0-beta | 50 | 2/1/2026 |
| 0.0.11-beta | 49 | 1/30/2026 |
| 0.0.10-beta | 45 | 1/30/2026 |
| 0.0.9-beta | 85 | 1/30/2026 |
| 0.0.8-beta | 50 | 1/30/2026 |
| 0.0.7-beta | 50 | 1/29/2026 |
| 0.0.6-beta | 49 | 1/29/2026 |
| 0.0.5-beta | 56 | 1/26/2026 |
| 0.0.4-beta | 55 | 1/26/2026 |
| 0.0.3-beta | 53 | 1/25/2026 |