Fuzn.FluentHttp 0.0.6-beta

This is a prerelease version of Fuzn.FluentHttp.
There is a newer prerelease version of this package available.
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
                    
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="Fuzn.FluentHttp" Version="0.0.6-beta" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fuzn.FluentHttp" Version="0.0.6-beta" />
                    
Directory.Packages.props
<PackageReference Include="Fuzn.FluentHttp" />
                    
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 Fuzn.FluentHttp --version 0.0.6-beta
                    
#r "nuget: Fuzn.FluentHttp, 0.0.6-beta"
                    
#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 Fuzn.FluentHttp@0.0.6-beta
                    
#: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=Fuzn.FluentHttp&version=0.0.6-beta&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Fuzn.FluentHttp&version=0.0.6-beta&prerelease
                    
Install as a Cake Tool

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 SerializerOptions and SerializerProvider are set, SerializerProvider takes precedence and SerializerOptions is 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.Data first, then only set defaults if not already configured. For async operations like token refresh, use a DelegatingHandler instead.

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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