![]() |
VOOZH | about |
dotnet add package Pathoschild.Http.FluentClient --version 4.4.2
NuGet\Install-Package Pathoschild.Http.FluentClient -Version 4.4.2
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.4.2" />
<PackageVersion Include="Pathoschild.Http.FluentClient" Version="4.4.2" />Directory.Packages.props
<PackageReference Include="Pathoschild.Http.FluentClient" />Project file
paket add Pathoschild.Http.FluentClient --version 4.4.2
#r "nuget: Pathoschild.Http.FluentClient, 4.4.2"
#:package Pathoschild.Http.FluentClient@4.4.2
#addin nuget:?package=Pathoschild.Http.FluentClient&version=4.4.2Install as a Cake Addin
#tool nuget:?package=Pathoschild.Http.FluentClient&version=4.4.2Install as a Cake Tool
FluentHttpClient is a modern async HTTP client for REST APIs. Its fluent interface lets you send an HTTP request and parse the response in one go — hiding away the gritty details like deserialisation, content negotiation, optional retry logic, and URL encoding:
Blog result = await new FluentClient("https://example.org/api")
.GetAsync("blogs")
.WithArgument("id", 15)
.WithBearerAuthentication(token)
.As<Blog>();
Designed with discoverability and extensibility as core principles, just autocomplete to see which methods are available at each step.
Install it from NuGet:
Install-Package Pathoschild.Http.FluentClient
The client works on most platforms (including Linux, Mac, and Windows):
| platform | min version |
|---|---|
| .NET | 5.0 |
| .NET Core | 1.0 |
| .NET Framework | 4.5.2 |
| .NET Standard | 1.3 |
| Mono | 4.6 |
| Unity | 2018.1 |
| Universal Windows Platform | 10.0 |
| Xamarin.Android | 7.0 |
| Xamarin.iOS | 10.0 |
| Xamarin.Mac | 3.0 |
Just create the client and chain methods to set up the request/response. For example, this
sends a GET request and deserializes the response into a custom Item class based on content
negotiation:
Item item = await new FluentClient()
.GetAsync("https://example.org/api/items/14")
.As<Item>();
You can also reuse the client for many requests (which improves performance using the built-in connection pool), and set a base URL in the constructor:
using var client = new FluentClient("https://example.org/api");
Item item = await client
.GetAsync("items/14")
.As<Item>();
The client provides methods for DELETE, GET, POST, PUT, and PATCH out of the box. You can
also use SendAsync to craft a custom HTTP request.
You can add any number of arguments to the request URL with an anonymous object:
await client
.PostAsync("items/14")
.WithArguments(new { page = 1, search = "some search text" });
Or with a dictionary:
await client
.PostAsync("items/14")
.WithArguments(new Dictionary<string, object> { … });
Or individually:
await client
.PostAsync("items/14")
.WithArgument("page", 1)
.WithArgument("search", "some search text");
You can add a model body directly in a POST or PUT:
await client.PostAsync("search", new SearchOptions(…));
Or add it to any request:
await client
.GetAsync("search")
.WithBody(new SearchOptions(…));
Or provide it in various formats:
| format | example |
|---|---|
| serialized model | WithBody(new ExampleModel()) |
| form URL encoded | WithBody(p => p.FormUrlEncoded(values)) |
| file upload | WithBody(p => p.FileUpload(files)) |
HttpContent |
WithBody(httpContent) |
You can add any number of headers:
await client
.PostAsync("items/14")
.WithHeader("User-Agent", "Some Bot/1.0.0")
.WithHeader("Content-Type", "application/json");
Or use methods for common headers like WithAuthentication, WithBasicAuthentication,
WithBearerAuthentication, and SetUserAgent.
(Basic headers like Content-Type and User-Agent will be added automatically if you omit them.)
You can parse the response by awaiting an As* method:
await client
.GetAsync("items")
.AsArray<Item>();
Here are the available formats:
| type | method |
|---|---|
Item |
As<Item>() |
Item[] |
AsArray<Item>() |
byte[] |
AsByteArray() |
string |
AsString() |
Stream |
AsStream() |
JToken |
AsRawJson() |
JObject |
AsRawJsonObject() |
JArray |
AsRawJsonArray() |
The AsRawJson method can also return dynamic to avoid needing a model class:
dynamic item = await client
.GetAsync("items/14")
.AsRawJsonObject();
string author = item.Author.Name;
If you don't need the content, you can just await the request:
await client.PostAsync("items", new Item(…));
You can also read the HTTP response info before parsing the body:
IResponse response = await client.GetAsync("items");
if (response.IsSuccessStatusCode || response.Status == HttpStatusCode.Found)
return response.AsArray<Item>();
By default the client will throw ApiException if the server returns an error code:
try
{
await client.Get("items");
}
catch(ApiException ex)
{
string responseText = await ex.Response.AsString();
throw new Exception($"The API responded with HTTP {ex.Response.Status}: {responseText}");
}
If you don't want that, you can...
disable it for one request:
IResponse response = await client
.GetAsync("items")
.WithOptions(ignoreHttpErrors: true);
disable it for all requests:
client.SetOptions(ignoreHttpErrors: true);
You can customize the request/response flow using a few built-in options.
You can set an option for one request:
IResponse response = await client
.GetAsync("items")
.WithOptions(ignoreHttpErrors: true);
Or for all requests:
client.SetOptions(ignoreHttpErrors: true);
The available options are:
| option | default | effect |
|---|---|---|
ignoreHttpErrors |
false |
Whether HTTP error responses like HTTP 404 should be ignored (true) or raised as exceptions (false). |
ignoreNullArguments |
true |
Whether null arguments in the request body and URL query string should be ignored (true) or sent as-is (false). |
completeWhen |
ResponseContentRead |
When we should stop waiting for the response. For example, setting this to ResponseHeadersRead will let you handle the response as soon as the headers are received, before the full response body has been fetched. This only affects getting the IResponse; reading the response body (e.g. using a method like IResponse.As<T>()) will still wait for the request body to be fetched as usual. |
The client won't retry failed requests by default, but that's easy to configure:
client
.SetRequestCoordinator(
maxRetries: 3,
shouldRetry: request => request.StatusCode != HttpStatusCode.OK,
getDelay: (attempt, response) => TimeSpan.FromSeconds(attempt * 5) // wait 5, 10, and 15 seconds
);
You can also wrap retry logic into IRetryConfig implementations:
/// <summary>A retry policy which retries with incremental backoff.</summary>
public class RetryWithBackoffConfig : IRetryConfig
{
/// <summary>The maximum number of times to retry a request before failing.</summary>
public int MaxRetries => 3;
/// <summary>Get whether a request should be retried.</summary>
/// <param name="response">The last HTTP response received.</param>
public bool ShouldRetry(HttpResponseMessage response)
{
return request.StatusCode != HttpStatusCode.OK;
}
/// <summary>Get the time to wait until the next retry.</summary>
/// <param name="attempt">The retry index (starting at 1).</param>
/// <param name="response">The last HTTP response received.</param>
public TimeSpan GetDelay(int attempt, HttpResponseMessage response)
{
return TimeSpan.FromSeconds(attempt * 5); // wait 5, 10, and 15 seconds
}
}
Then you can add one or more retry policies, and they'll each be given the opportunity to retry a request:
client
.SetRequestCoordinator(new[]
{
new TokenExpiredRetryConfig(),
new DatabaseTimeoutRetryConfig(),
new RetryWithBackoffConfig()
});
Note that there's one retry count across all retry policies. For example, if
TokenExpiredRetryConfig retries once before falling back to RetryWithBackoffConfig, the latter
will receive 2 as its first retry count. If you need more granular control, see custom
retry/coordination policy.
The client fully supports .NET cancellation tokens if you need to cancel requests:
using var tokenSource = new CancellationTokenSource();
await client
.PostAsync(…)
.WithCancellationToken(tokenSource.Token);
tokenSource.Cancel();
The cancellation token is used for the whole process, from sending the request to reading the
response. You can change the cancellation token on the response if needed, by awaiting the request
and calling WithCancellationToken on the response.
You can make changes directly to the HTTP request before it's sent:
client
.GetAsync("items")
.WithCustom(request =>
{
request.Method = HttpMethod.Post;
request.Headers.CacheControl = new CacheControlHeaderValue { MaxAge = TimeSpan.FromMinutes(30) };
});
The client is built around the async and await keywords, but you can use the client
synchronously. That's not recommended — it complicates error-handling (e.g. errors get wrapped
into AggregateException), and it's very easy to cause thread deadlocks when you do this (see
Parallel Programming with .NET: Await, and UI, and deadlocks! Oh my! and
Don't Block on Async Code).
If you really need to use it synchronously, you can just call the Result property:
Item item = client.GetAsync("items/14").Result;
Or if you don't need the response:
client.PostAsync("items", new Item(…)).AsResponse().Wait();
The client supports JSON and XML out of the box. If you need more, you can...
Add any of the existing media type formatters:
client.Formatters.Add(new YamlFormatter());
Create your own by subclassing MediaTypeFormatter (optionally using the included
MediaTypeFormatterBase class).
You can read and change the underlying HTTP requests and responses by creating IHttpFilter
implementations. They can be useful for automating custom authentication or error-handling.
For example, the default error-handling is just a filter:
/// <summary>Method invoked just after the HTTP response is received. This method can modify the incoming HTTP response.</summary>
/// <param name="response">The HTTP response.</param>
/// <param name="httpErrorAsException">Whether HTTP error responses (e.g. HTTP 404) should be raised as exceptions.</param>
public void OnResponse(IResponse response, bool httpErrorAsException)
{
if (httpErrorAsException && !response.Message.IsSuccessStatusCode)
throw new ApiException(response, $"The API query failed with status code {response.Message.StatusCode}: {response.Message.ReasonPhrase}");
}
...which you can replace with your own:
client.Filters.Remove<DefaultErrorFilter>();
client.Filters.Add(new YourErrorFilter());
You can do much more with HTTP filters by editing the requests before they're sent or the responses before they're parsed:
/// <summary>Method invoked just before the HTTP request is submitted. This method can modify the outgoing HTTP request.</summary>
/// <param name="request">The HTTP request.</param>
public void OnRequest(IRequest request)
{
// example only — you'd normally use a method like client.SetAuthentication(…) instead.
request.Message.Headers.Authorization = new AuthenticationHeaderValue("token", "…");
}
You can implement IRequestCoordinator to control how requests are dispatched. For example, here's
a retry coordinator using Polly:
/// <summary>A request coordinator which retries failed requests with a delay between each attempt.</summary>
public class RetryCoordinator : IRequestCoordinator
{
/// <summary>Dispatch an HTTP request.</summary>
/// <param name="request">The response message to validate.</param>
/// <param name="send">Dispatcher that executes the request.</param>
/// <returns>The final HTTP response.</returns>
public Task<HttpResponseMessage> ExecuteAsync(IRequest request, Func<IRequest, Task<HttpResponseMessage>> send)
{
HttpStatusCode[] retryCodes = { HttpStatusCode.GatewayTimeout, HttpStatusCode.RequestTimeout };
return Policy
.HandleResult<HttpResponseMessage>(request => retryCodes.Contains(request.StatusCode)) // should we retry?
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(attempt)) // up to 3 retries with increasing delay
.ExecuteAsync(() => send(request)); // begin handling request
}
}
...and here's how you'd set it:
client.SetRequestCoordinator(new RetryCoordinator());
(You can only have one request coordinator on the client; you should use HTTP filters instead for most overrides.)
For advanced scenarios, you can customise the underlying HttpClient and HttpClientHandler. See the next section for an example.
Here's how to create mock requests for unit testing using RichardSzalay.MockHttp:
// create mock
var mockHandler = new MockHttpMessageHandler();
mockHandler.When(HttpMethod.Get, "https://example.org/api/items").Respond(HttpStatusCode.OK, testRequest => new StringContent("[]"));
// create client
var client = new FluentClient("https://example.org/api", new HttpClient(mockHandler));
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 is compatible. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp1.0 netcoreapp1.0 was computed. netcoreapp1.1 netcoreapp1.1 was computed. netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard1.3 netstandard1.3 is compatible. netstandard1.4 netstandard1.4 was computed. netstandard1.5 netstandard1.5 was computed. netstandard1.6 netstandard1.6 was computed. netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net452 net452 is compatible. net46 net46 was computed. net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen30 tizen30 was computed. tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Universal Windows Platform | uap uap was computed. uap10.0 uap10.0 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 5 NuGet packages that depend on Pathoschild.Http.FluentClient:
| Package | Downloads |
|---|---|
|
StrongGrid
StrongGrid is a strongly typed .NET client for SendGrid's v3 API. |
|
|
ZoomNet
ZoomNet is a strongly typed .NET client for Zoom's API. |
|
|
CakeMail.RestClient
CakeMail.RestClient is a .NET wrapper for the CakeMail API |
|
|
PowerDiary.ZoomNet
ZoomNet is a strongly typed .NET client for Zoom's API. |
|
|
Pathoschild.FluentNexus
A modern async HTTP client for the Nexus Mods API. It gives you simple strongly-typed methods to access all of the Nexus API endpoints, handling the gritty details like deserialisation, content negotiation, URL encoding, and error-handling. |
Showing the top 5 popular GitHub repositories that depend on Pathoschild.Http.FluentClient:
| Repository | Stars |
|---|---|
|
Pathoschild/SMAPI
The modding API for Stardew Valley.
|
|
|
Nexus-Mods/NexusMods.App
Home of the development of the Nexus Mods App
|
|
| Nexus-Mods/Nexus-Mod-Manager | |
|
Jericho/StrongGrid
Strongly typed library for the entire SendGrid v3 API, including webhooks
|
|
|
qwqcode/Nacollector
Nacollector is a platform for web data collection.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 4.4.2 | 220,593 | 6/2/2025 |
| 4.4.1 | 585,690 | 6/6/2024 |
| 4.4.0 | 233,597 | 5/24/2024 |
| 4.3.0 | 772,960 | 3/14/2023 |
| 4.2.0 | 586,932 | 10/7/2022 |
| 4.1.1 | 105,349 | 6/23/2022 |
| 4.1.0 | 1,284,906 | 3/11/2021 |
| 4.0.0 | 736,045 | 5/14/2020 |
| 3.3.1 | 355,254 | 7/20/2019 |
| 3.3.0 | 232,681 | 4/28/2019 |
| 3.2.0 | 576,860 | 4/18/2018 |
| 3.1.0 | 72,254 | 9/19/2017 |
| 3.0.0 | 67,465 | 2/9/2017 |
| 2.3.0 | 9,700 | 12/12/2016 |
| 2.2.0 | 23,568 | 6/30/2016 |
| 2.1.0 | 8,886 | 5/8/2016 |
| 2.0.0 | 8,483 | 4/28/2016 |
| 1.2.1 | 10,217 | 10/28/2015 |
| 1.2.0 | 15,699 | 10/30/2013 |
| 1.1.0 | 10,203 | 8/28/2013 |
See release notes at https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#442