Fuzn.FluentHttp
0.0.6-beta
See the version list below for details.
dotnet add package Fuzn.FluentHttp --version 0.0.6-beta
NuGet\Install-Package Fuzn.FluentHttp -Version 0.0.6-beta
<PackageReference Include="Fuzn.FluentHttp" Version="0.0.6-beta" />
<PackageVersion Include="Fuzn.FluentHttp" Version="0.0.6-beta" />
<PackageReference Include="Fuzn.FluentHttp" />
paket add Fuzn.FluentHttp --version 0.0.6-beta
#r "nuget: Fuzn.FluentHttp, 0.0.6-beta"
#:package Fuzn.FluentHttp@0.0.6-beta
#addin nuget:?package=Fuzn.FluentHttp&version=0.0.6-beta&prerelease
#tool nuget:?package=Fuzn.FluentHttp&version=0.0.6-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.Body}");
}
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>();
Request Body
var response = await httpClient
.Url("https://api.example.com/users")
.Body(new { Name = "John", Email = "john@example.com" })
.Post<User>();
Query Parameters
// Single parameter
var response = await httpClient
.Url("https://api.example.com/search")
.QueryParam("q", "dotnet")
.QueryParam("page", 1)
.Get<SearchResult>();
// Multiple parameters via dictionary
var response = await httpClient
.Url("https://api.example.com/search")
.QueryParams(new Dictionary<string, object?>
{
["q"] = "dotnet",
["page"] = 1
})
.Get<SearchResult>();
// Anonymous object
var response = await httpClient
.Url("https://api.example.com/search")
.QueryParams(new { q = "dotnet", page = 1 })
.Get<SearchResult>();
// Multiple values for same parameter
var response = await httpClient
.Url("https://api.example.com/items")
.QueryParam("tags", new[] { "c#", "dotnet", "http" })
.Get<ItemList>();
Headers
var response = await httpClient
.Url("https://api.example.com/data")
.Header("X-Custom-Header", "value")
.Headers(new Dictionary<string, string>
{
["X-Another"] = "another-value"
})
.Get<Data>();
Authentication
// Bearer token
var response = await httpClient
.Url("https://api.example.com/protected")
.AuthBearer("your-jwt-token")
.Get<ProtectedData>();
// Basic authentication
var response = await httpClient
.Url("https://api.example.com/protected")
.AuthBasic("username", "password")
.Get<ProtectedData>();
// API Key
var response = await httpClient
.Url("https://api.example.com/protected")
.AuthApiKey("your-api-key")
.Get<ProtectedData>();
// Custom header name for API key
var response = await httpClient
.Url("https://api.example.com/protected")
.AuthApiKey("your-api-key", "Authorization")
.Get<ProtectedData>();
Content Types
var response = await httpClient
.Url("https://api.example.com/data")
.ContentType(ContentTypes.Json)
.Body(data)
.Post<Result>();
// Custom content type
var response = await httpClient
.Url("https://api.example.com/graphql")
.ContentType("application/graphql")
.Body(query)
.Post<GraphQLResponse>();
Accept Headers
var response = await httpClient
.Url("https://api.example.com/data")
.Accept(AcceptTypes.Json)
.Get<Data>();
// Custom accept type
var response = await httpClient
.Url("https://api.example.com/report")
.Accept("application/pdf")
.Get();
File Uploads
// Upload file from stream
var response = await httpClient
.Url("https://api.example.com/upload")
.File("file", "document.pdf", fileStream, "application/pdf")
.Post<UploadResult>();
// Upload file from byte array
var response = await httpClient
.Url("https://api.example.com/upload")
.File("file", "image.png", imageBytes, "image/png")
.Post<UploadResult>();
// Multiple files with form fields
var response = await httpClient
.Url("https://api.example.com/upload")
.File("file1", "doc1.pdf", stream1)
.File("file2", "doc2.pdf", stream2)
.FormField("description", "My documents")
.Post<UploadResult>();
Cookies
var response = await httpClient
.Url("https://api.example.com/data")
.Cookie("session", "abc123")
.Cookie("preference", "dark-mode", path: "/", duration: TimeSpan.FromDays(30))
.Get<Data>();
Timeouts
var response = await httpClient
.Url("https://api.example.com/slow-endpoint")
.Timeout(TimeSpan.FromSeconds(30))
.Get<Data>();
Custom User-Agent
var response = await httpClient
.Url("https://api.example.com/data")
.UserAgent("MyApp/1.0")
.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.As<User>();
// Get raw body
string body = response.Body;
// Get as bytes
byte[] bytes = response.AsBytes();
}
// Access status code
HttpStatusCode status = response.StatusCode;
// Access headers
var headers = response.Headers;
var contentHeaders = response.ContentHeaders;
// Access cookies
var cookies = response.Cookies;
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;
// Raw body
string body = response.Body;
// Headers and cookies
var headers = response.Headers;
var cookies = response.Cookies;
// Deserialize to a different type
var error = response.As<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")
.Body(new { Name = "John" })
.AuthBearer("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
// Body: {"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")
.Body(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")
.SerializerOptions(options)
.Body(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")
.SerializerProvider(new NewtonsoftSerializerProvider())
.Body(data)
.Post<Result>();
Note: When both
SerializerOptionsandSerializerProviderare set,SerializerProvidertakes precedence andSerializerOptionsis 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.SerializerOptions(globalOptions);
}
};
Adding Default Headers
FluentHttpDefaults.BeforeSend = builder =>
{
// Add correlation ID to all requests
if (!builder.Data.Headers.ContainsKey("X-Correlation-Id"))
{
builder.Header("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.SerializerProvider(new LegacySerializerProvider());
}
// Longer timeout for report endpoints
if (builder.Data.RequestUrl.Contains("/reports/"))
{
builder.Timeout(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.
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")
.CancellationToken(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 |