{"id":17761686,"url":"https://github.com/pathoschild/fluenthttpclient","last_synced_at":"2025-05-15T14:07:17.459Z","repository":{"id":3284426,"uuid":"4324826","full_name":"Pathoschild/FluentHttpClient","owner":"Pathoschild","description":"A modern async HTTP client for REST APIs. Its fluent interface lets you send an HTTP request and parse the response in one go.","archived":false,"fork":false,"pushed_at":"2024-07-07T18:11:32.000Z","size":1351,"stargazers_count":345,"open_issues_count":3,"forks_count":52,"subscribers_count":18,"default_branch":"develop","last_synced_at":"2024-11-14T15:20:46.403Z","etag":null,"topics":["http","http-client","rest"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Pathoschild.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-05-14T14:23:55.000Z","updated_at":"2024-11-02T03:51:14.000Z","dependencies_parsed_at":"2024-05-31T04:49:36.878Z","dependency_job_id":"81835c15-9be4-45c6-82ef-058014a9a004","html_url":"https://github.com/Pathoschild/FluentHttpClient","commit_stats":{"total_commits":322,"total_committers":15,"mean_commits":"21.466666666666665","dds":0.6863354037267081,"last_synced_commit":"1685e7be37d742d7a6f6f5788ddacf7cb21de933"},"previous_names":["pathoschild/pathoschild.fluenthttpclient"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pathoschild%2FFluentHttpClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pathoschild%2FFluentHttpClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pathoschild%2FFluentHttpClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pathoschild%2FFluentHttpClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Pathoschild","download_url":"https://codeload.github.com/Pathoschild/FluentHttpClient/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247704569,"owners_count":20982298,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","http-client","rest"],"created_at":"2024-10-26T19:42:46.456Z","updated_at":"2025-04-07T18:10:03.278Z","avatar_url":"https://github.com/Pathoschild.png","language":"C#","readme":"**FluentHttpClient** is a modern async HTTP client for REST APIs. Its fluent interface lets\r\nyou send an HTTP request and parse the response in one go — hiding away the gritty\r\ndetails like deserialisation, content negotiation, optional retry logic, and URL encoding:\r\n\r\n```c#\r\nBlog result = await new FluentClient(\"https://example.org/api\")\r\n   .GetAsync(\"blogs\")\r\n   .WithArgument(\"id\", 15)\r\n   .WithBearerAuthentication(token)\r\n   .As\u003cBlog\u003e();\r\n```\r\n\r\nDesigned with discoverability and extensibility as core principles, just autocomplete to see which\r\nmethods are available at each step.\r\n\r\n## Contents\r\n* [Get started](#get-started)\r\n  * [Install](#install)\r\n  * [Basic usage](#basic-usage)\r\n  * [URL arguments](#url-arguments)\r\n  * [Body](#body)\r\n  * [Headers](#headers)\r\n  * [Read the response](#read-the-response)\r\n  * [Read the response info](#read-the-response-info)\r\n  * [Handle errors](#handle-errors)\r\n* [Advanced features](#advanced-features)\r\n  * [Request options](#request-options)\r\n  * [Simple retry policy](#simple-retry-policy)\r\n  * [Cancellation tokens](#cancellation-tokens)\r\n  * [Custom requests](#custom-requests)\r\n  * [Synchronous use](#synchronous-use)\r\n* [Extensibility](#extensibility)\r\n  * [Custom formats](#custom-formats)\r\n  * [Custom filters](#custom-filters)\r\n  * [Custom retry/coordination policy](#custom-retrycoordination-policy)\r\n  * [Custom HTTP](#custom-http)\r\n  * [Mocks for unit testing](#mocks-for-unit-testing)\r\n\r\n## Get started\r\n### Install\r\nInstall it [from NuGet][Pathoschild.Http.FluentClient]:\r\n\u003e Install-Package Pathoschild.Http.FluentClient\r\n\r\nThe client works on most platforms (including Linux, Mac, and Windows):\r\n\r\n| platform                    | min version |\r\n| :-------------------------- | :---------- |\r\n| .NET                        | 5.0         |\r\n| .NET Core                   | 1.0         |\r\n| .NET Framework              | 4.5.2       |\r\n| [.NET Standard][]           | 1.3         |\r\n| Mono                        | 4.6         |\r\n| Unity                       | 2018.1      |\r\n| Universal Windows Platform  | 10.0        |\r\n| Xamarin.Android             | 7.0         |\r\n| Xamarin.iOS                 | 10.0        |\r\n| Xamarin.Mac                 | 3.0         |\r\n\r\n### Basic usage\r\nJust create the client and chain methods to set up the request/response. For example, this\r\nsends a `GET` request and deserializes the response into a custom `Item` class based on content\r\nnegotiation:\r\n```c#\r\nItem item = await new FluentClient()\r\n   .GetAsync(\"https://example.org/api/items/14\")\r\n   .As\u003cItem\u003e();\r\n```\r\n\r\nYou can also reuse the client for many requests (which improves performance using the built-in\r\nconnection pool), and set a base URL in the constructor:\r\n```c#\r\nusing var client = new FluentClient(\"https://example.org/api\");\r\n\r\nItem item = await client\r\n   .GetAsync(\"items/14\")\r\n   .As\u003cItem\u003e();\r\n```\r\n\r\nThe client provides methods for `DELETE`, `GET`, `POST`, `PUT`, and `PATCH` out of the box. You can\r\nalso use `SendAsync` to craft a custom HTTP request.\r\n\r\n### URL arguments\r\nYou can add any number of arguments to the request URL with an anonymous object:\r\n```c#\r\nawait client\r\n   .PostAsync(\"items/14\")\r\n   .WithArguments(new { page = 1, search = \"some search text\" });\r\n```\r\n\r\nOr with a dictionary:\r\n```c#\r\nawait client\r\n   .PostAsync(\"items/14\")\r\n   .WithArguments(new Dictionary\u003cstring, object\u003e { … });\r\n```\r\n\r\nOr individually:\r\n```c#\r\nawait client\r\n   .PostAsync(\"items/14\")\r\n   .WithArgument(\"page\", 1)\r\n   .WithArgument(\"search\", \"some search text\");\r\n```\r\n\r\n### Body\r\nYou can add a model body directly in a POST or PUT:\r\n```c#\r\nawait client.PostAsync(\"search\", new SearchOptions(…));\r\n```\r\n\r\nOr add it to any request:\r\n```c#\r\nawait client\r\n   .GetAsync(\"search\")\r\n   .WithBody(new SearchOptions(…));\r\n```\r\n\r\nOr provide it in various formats:\r\n\r\nformat           | example\r\n:--------------- | :------\r\nserialized model | `WithBody(new ExampleModel())`\r\nform URL encoded | `WithBody(p =\u003e p.FormUrlEncoded(values))`\r\nfile upload      | `WithBody(p =\u003e p.FileUpload(files))`\r\n[`HttpContent`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httprequestmessage.content#remarks) | `WithBody(httpContent)`\r\n\r\n### Headers\r\nYou can add any number of headers:\r\n```c#\r\nawait client\r\n   .PostAsync(\"items/14\")\r\n   .WithHeader(\"User-Agent\", \"Some Bot/1.0.0\")\r\n   .WithHeader(\"Content-Type\", \"application/json\");\r\n```\r\n\r\nOr use methods for common headers like `WithAuthentication`, `WithBasicAuthentication`,\r\n`WithBearerAuthentication`, and `SetUserAgent`.\r\n\r\n(Basic headers like `Content-Type` and `User-Agent` will be added automatically if you omit them.)\r\n\r\n### Read the response\r\nYou can parse the response by awaiting an `As*` method:\r\n```c#\r\nawait client\r\n   .GetAsync(\"items\")\r\n   .AsArray\u003cItem\u003e();\r\n```\r\n\r\nHere are the available formats:\r\n\r\ntype      | method\r\n--------- | ------\r\n`Item`    | `As\u003cItem\u003e()`\r\n`Item[]`  | `AsArray\u003cItem\u003e()`\r\n`byte[]`  | `AsByteArray()`\r\n`string`  | `AsString()`\r\n`Stream`  | `AsStream()`\r\n`JToken`  | `AsRawJson()`\r\n`JObject` | `AsRawJsonObject()`\r\n`JArray`  | `AsRawJsonArray()`\r\n\r\nThe `AsRawJson` method can also return `dynamic` to avoid needing a model class:\r\n```c#\r\ndynamic item = await client\r\n   .GetAsync(\"items/14\")\r\n   .AsRawJsonObject();\r\n\r\nstring author = item.Author.Name;\r\n```\r\n\r\nIf you don't need the content, you can just await the request:\r\n```c#\r\nawait client.PostAsync(\"items\", new Item(…));\r\n```\r\n\r\n### Read the response info\r\nYou can also read the HTTP response info before parsing the body:\r\n\r\n```c#\r\nIResponse response = await client.GetAsync(\"items\");\r\nif (response.IsSuccessStatusCode || response.Status == HttpStatusCode.Found)\r\n   return response.AsArray\u003cItem\u003e();\r\n```\r\n\r\n### Handle errors\r\nBy default the client will throw `ApiException` if the server returns an error code:\r\n```c#\r\ntry\r\n{\r\n   await client.Get(\"items\");\r\n}\r\ncatch(ApiException ex)\r\n{\r\n   string responseText = await ex.Response.AsString();\r\n   throw new Exception($\"The API responded with HTTP {ex.Response.Status}: {responseText}\");\r\n}\r\n```\r\n\r\nIf you don't want that, you can...\r\n* disable it for one request:\r\n\r\n  ```c#\r\n  IResponse response = await client\r\n     .GetAsync(\"items\")\r\n     .WithOptions(ignoreHttpErrors: true);\r\n  ```\r\n\r\n* disable it for all requests:\r\n\r\n  ```c#\r\n  client.SetOptions(ignoreHttpErrors: true);\r\n  ```\r\n\r\n* [use your own error filter](#custom-filters).\r\n\r\n## Advanced features\r\n### Request options\r\nYou can customize the request/response flow using a few built-in options.\r\n\r\nYou can set an option for one request:\r\n```c#\r\nIResponse response = await client\r\n   .GetAsync(\"items\")\r\n   .WithOptions(ignoreHttpErrors: true);\r\n```\r\n\r\nOr for all requests:\r\n```c#\r\nclient.SetOptions(ignoreHttpErrors: true);\r\n```\r\n\r\nThe available options are:\r\n\r\noption                | default | effect\r\n--------------------- | ------- | ------\r\n`ignoreHttpErrors`    | `false` | Whether HTTP error responses like HTTP 404 should be ignored (`true`) or raised as exceptions (`false`).\r\n`ignoreNullArguments` | `true`  | Whether null arguments in the request body and URL query string should be ignored (`true`) or sent as-is (`false`).\r\n`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\u003cT\u003e()`) will still wait for the request body to be fetched as usual.\r\n\r\n### Simple retry policy\r\nThe client won't retry failed requests by default, but that's easy to configure:\r\n```c#\r\nclient\r\n   .SetRequestCoordinator(\r\n      maxRetries: 3,\r\n      shouldRetry: request =\u003e request.StatusCode != HttpStatusCode.OK,\r\n      getDelay: (attempt, response) =\u003e TimeSpan.FromSeconds(attempt * 5) // wait 5, 10, and 15 seconds\r\n   );\r\n```\r\n\r\n### Chained retry policies\r\nYou can also wrap retry logic into `IRetryConfig` implementations:\r\n\r\n```c#\r\n/// \u003csummary\u003eA retry policy which retries with incremental backoff.\u003c/summary\u003e\r\npublic class RetryWithBackoffConfig : IRetryConfig\r\n{\r\n    /// \u003csummary\u003eThe maximum number of times to retry a request before failing.\u003c/summary\u003e\r\n    public int MaxRetries =\u003e 3;\r\n\r\n    /// \u003csummary\u003eGet whether a request should be retried.\u003c/summary\u003e\r\n    /// \u003cparam name=\"response\"\u003eThe last HTTP response received.\u003c/param\u003e\r\n    public bool ShouldRetry(HttpResponseMessage response)\r\n    {\r\n        return request.StatusCode != HttpStatusCode.OK;\r\n    }\r\n\r\n    /// \u003csummary\u003eGet the time to wait until the next retry.\u003c/summary\u003e\r\n    /// \u003cparam name=\"attempt\"\u003eThe retry index (starting at 1).\u003c/param\u003e\r\n    /// \u003cparam name=\"response\"\u003eThe last HTTP response received.\u003c/param\u003e\r\n    public TimeSpan GetDelay(int attempt, HttpResponseMessage response)\r\n    {\r\n        return TimeSpan.FromSeconds(attempt * 5); // wait 5, 10, and 15 seconds\r\n    }\r\n}\r\n```\r\n\r\nThen you can add one or more retry policies, and they'll each be given the opportunity to retry\r\na request:\r\n\r\n```c#\r\nclient\r\n   .SetRequestCoordinator(new[]\r\n   {\r\n      new TokenExpiredRetryConfig(),\r\n      new DatabaseTimeoutRetryConfig(),\r\n      new RetryWithBackoffConfig()\r\n   });\r\n```\r\n\r\nNote that there's one retry count across all retry policies. For example, if\r\n`TokenExpiredRetryConfig` retries once before falling back to `RetryWithBackoffConfig`, the latter\r\nwill receive `2` as its first retry count. If you need more granular control, see [_custom\r\nretry/coordination policy_](#custom-retrycoordination-policy).\r\n\r\n### Cancellation tokens\r\nThe client fully supports [.NET cancellation tokens][] if you need to cancel requests:\r\n\r\n```c#\r\nusing var tokenSource = new CancellationTokenSource();\r\n\r\nawait client\r\n   .PostAsync(…)\r\n   .WithCancellationToken(tokenSource.Token);\r\n\r\ntokenSource.Cancel();\r\n```\r\n\r\nThe cancellation token is used for the whole process, from sending the request to reading the\r\nresponse. You can change the cancellation token on the response if needed, by awaiting the request\r\nand calling `WithCancellationToken` on the response.\r\n\r\n### Custom requests\r\nYou can make changes directly to the HTTP request before it's sent:\r\n```c#\r\nclient\r\n   .GetAsync(\"items\")\r\n   .WithCustom(request =\u003e\r\n   {\r\n      request.Method = HttpMethod.Post;\r\n      request.Headers.CacheControl = new CacheControlHeaderValue { MaxAge = TimeSpan.FromMinutes(30) };\r\n   });\r\n```\r\n\r\n### Synchronous use\r\nThe client is built around the `async` and `await` keywords, but you can use the client\r\nsynchronously. That's not recommended — it complicates error-handling (e.g. errors get wrapped\r\ninto [AggregateException][]), and it's very easy to cause thread deadlocks when you do this (see\r\n_[Parallel Programming with .NET: Await, and UI, and deadlocks! Oh my!][]_ and\r\n_[Don't Block on Async Code][])._\r\n\r\nIf you really need to use it synchronously, you can just call the `Result` property:\r\n```c#\r\nItem item = client.GetAsync(\"items/14\").Result;\r\n```\r\n\r\nOr if you don't need the response:\r\n\r\n```c#\r\nclient.PostAsync(\"items\", new Item(…)).AsResponse().Wait();\r\n```\r\n\r\n## Extensibility\r\n### Custom formats\r\nThe client supports JSON and XML out of the box. If you need more, you can...\r\n\r\n* Add any of the existing [media type formatters][]:\r\n\r\n  ```c#\r\n  client.Formatters.Add(new YamlFormatter());\r\n  ```\r\n\r\n* Create your own by subclassing `MediaTypeFormatter` (optionally using the included\r\n  `MediaTypeFormatterBase` class).\r\n\r\n### Custom filters\r\nYou can read and change the underlying HTTP requests and responses by creating `IHttpFilter`\r\nimplementations. They can be useful for automating custom authentication or error-handling.\r\n\r\nFor example, the default error-handling is just a filter:\r\n```c#\r\n/// \u003csummary\u003eMethod invoked just after the HTTP response is received. This method can modify the incoming HTTP response.\u003c/summary\u003e\r\n/// \u003cparam name=\"response\"\u003eThe HTTP response.\u003c/param\u003e\r\n/// \u003cparam name=\"httpErrorAsException\"\u003eWhether HTTP error responses (e.g. HTTP 404) should be raised as exceptions.\u003c/param\u003e\r\npublic void OnResponse(IResponse response, bool httpErrorAsException)\r\n{\r\n   if (httpErrorAsException \u0026\u0026 !response.Message.IsSuccessStatusCode)\r\n      throw new ApiException(response, $\"The API query failed with status code {response.Message.StatusCode}: {response.Message.ReasonPhrase}\");\r\n}\r\n```\r\n\r\n...which you can replace with your own:\r\n```c#\r\nclient.Filters.Remove\u003cDefaultErrorFilter\u003e();\r\nclient.Filters.Add(new YourErrorFilter());\r\n```\r\n\r\nYou can do much more with HTTP filters by editing the requests before they're sent or the responses\r\nbefore they're parsed:\r\n\r\n```c#\r\n/// \u003csummary\u003eMethod invoked just before the HTTP request is submitted. This method can modify the outgoing HTTP request.\u003c/summary\u003e\r\n/// \u003cparam name=\"request\"\u003eThe HTTP request.\u003c/param\u003e\r\npublic void OnRequest(IRequest request)\r\n{\r\n   // example only — you'd normally use a method like client.SetAuthentication(…) instead.\r\n   request.Message.Headers.Authorization = new AuthenticationHeaderValue(\"token\", \"…\");\r\n}\r\n```\r\n\r\n### Custom retry/coordination policy\r\nYou can implement `IRequestCoordinator` to control how requests are dispatched. For example, here's\r\na retry coordinator using [Polly](https://github.com/App-vNext/Polly):\r\n```c#\r\n/// \u003csummary\u003eA request coordinator which retries failed requests with a delay between each attempt.\u003c/summary\u003e\r\npublic class RetryCoordinator : IRequestCoordinator\r\n{\r\n   /// \u003csummary\u003eDispatch an HTTP request.\u003c/summary\u003e\r\n   /// \u003cparam name=\"request\"\u003eThe response message to validate.\u003c/param\u003e\r\n   /// \u003cparam name=\"send\"\u003eDispatcher that executes the request.\u003c/param\u003e\r\n   /// \u003creturns\u003eThe final HTTP response.\u003c/returns\u003e\r\n   public Task\u003cHttpResponseMessage\u003e ExecuteAsync(IRequest request, Func\u003cIRequest, Task\u003cHttpResponseMessage\u003e\u003e send)\r\n   {\r\n      HttpStatusCode[] retryCodes = { HttpStatusCode.GatewayTimeout, HttpStatusCode.RequestTimeout };\r\n      return Policy\r\n         .HandleResult\u003cHttpResponseMessage\u003e(request =\u003e retryCodes.Contains(request.StatusCode)) // should we retry?\r\n         .WaitAndRetryAsync(3, attempt =\u003e TimeSpan.FromSeconds(attempt)) // up to 3 retries with increasing delay\r\n         .ExecuteAsync(() =\u003e send(request)); // begin handling request\r\n   }\r\n}\r\n```\r\n\r\n...and here's how you'd set it:\r\n```c#\r\nclient.SetRequestCoordinator(new RetryCoordinator());\r\n```\r\n\r\n(You can only have one request coordinator on the client; you should use [HTTP filters](#custom-filters)\r\ninstead for most overrides.)\r\n\r\n### Custom HTTP\r\nFor advanced scenarios, you can customise the underlying [HttpClient][] and\r\n[HttpClientHandler][]. See the next section for an example.\r\n\r\n### Mocks for unit testing\r\nHere's how to create mock requests for unit testing using\r\n[RichardSzalay.MockHttp](https://www.nuget.org/packages/RichardSzalay.MockHttp/):\r\n```c#\r\n// create mock\r\nvar mockHandler = new MockHttpMessageHandler();\r\nmockHandler.When(HttpMethod.Get, \"https://example.org/api/items\").Respond(HttpStatusCode.OK, testRequest =\u003e new StringContent(\"[]\"));\r\n\r\n// create client\r\nvar client = new FluentClient(\"https://example.org/api\", new HttpClient(mockHandler));\r\n```\r\n\r\n[.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library\r\n[Parallel Programming with .NET: Await, and UI, and deadlocks! Oh my!]: https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-and-ui-and-deadlocks-oh-my/\r\n[Don't Block on Async Code]: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html\r\n[media type formatters]: https://www.nuget.org/packages?q=MediaTypeFormatter\r\n[.NET cancellation tokens]: https://learn.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads\r\n\r\n[AggregateException]: https://docs.microsoft.com/en-us/dotnet/api/system.aggregateexception\r\n[HttpClient]: https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient\r\n[HttpClientHandler]: https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler\r\n[MediaTypeFormatter]: https://msdn.microsoft.com/en-us/library/system.net.http.formatting.mediatypeformatter.aspx\r\n\r\n[Json.NET]: https://www.newtonsoft.com/json\r\n[JSON]: https://en.wikipedia.org/wiki/JSON\r\n\r\n[Pathoschild.Http.FluentClient]: https://nuget.org/packages/Pathoschild.Http.FluentClient\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathoschild%2Ffluenthttpclient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpathoschild%2Ffluenthttpclient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathoschild%2Ffluenthttpclient/lists"}