{"id":13429520,"url":"https://github.com/reactiveui/refit","last_synced_at":"2026-02-06T03:04:11.505Z","repository":{"id":9654340,"uuid":"11591313","full_name":"reactiveui/refit","owner":"reactiveui","description":"The automatic type-safe REST library for .NET Core, Xamarin and .NET. Heavily inspired by Square's Retrofit library, Refit turns your REST API into a live interface.","archived":false,"fork":false,"pushed_at":"2025-08-27T02:34:49.000Z","size":15934,"stargazers_count":9220,"open_issues_count":197,"forks_count":766,"subscribers_count":164,"default_branch":"main","last_synced_at":"2025-09-04T14:40:45.283Z","etag":null,"topics":["c-sharp","dotnet","dotnet-core","http","json","xamarin","xml"],"latest_commit_sha":null,"homepage":"https://reactiveui.github.io/refit/","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/reactiveui.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":"CODE_OF_CONDUCT.md","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,"zenodo":null},"funding":{"github":["reactivemarbles"]}},"created_at":"2013-07-22T20:11:57.000Z","updated_at":"2025-09-03T07:13:08.000Z","dependencies_parsed_at":"2023-09-23T02:29:05.269Z","dependency_job_id":"d8b6f972-bdf6-4f7f-ad87-3417994427a9","html_url":"https://github.com/reactiveui/refit","commit_stats":{"total_commits":1318,"total_committers":148,"mean_commits":8.905405405405405,"dds":0.8186646433990895,"last_synced_commit":"040ecc6857337b419eb83d08a5c2929047eea20e"},"previous_names":["xpaulbettsx/refit","paulcbetts/refit"],"tags_count":71,"template":false,"template_full_name":null,"purl":"pkg:github/reactiveui/refit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactiveui%2Frefit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactiveui%2Frefit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactiveui%2Frefit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactiveui%2Frefit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reactiveui","download_url":"https://codeload.github.com/reactiveui/refit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactiveui%2Frefit/sbom","scorecard":{"id":359022,"data":{"date":"2025-08-11","repo":{"name":"github.com/reactiveui/refit","commit":"dcee3a85f16a1b54d788d62040ba69a860086efa"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":8,"reason":"10 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 8","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":9,"reason":"Found 14/15 approved changesets -- score normalized to 9","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci-build.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci-build.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/reactiveui/refit/ci-build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/lock.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/reactiveui/refit/lock.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/reactiveui/refit/release.yml/main?enable=pin","Warn: containerImage not pinned by hash: .devcontainers/Dockerfile:3","Warn: nugetCommand not pinned by hash: .devcontainers/post-create.sh:2: pin your dependecies by either enabling central package management (https://learn.microsoft.com/nuget/consume-packages/Central-Package-Management) or using a lockfile (https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies)","Info:   0 out of   3 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   1 nugetCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 7.2.22 not signed: https://api.github.com/repos/reactiveui/refit/releases/184364834","Warn: release artifact 8.0.0 not signed: https://api.github.com/repos/reactiveui/refit/releases/183236773","Warn: release artifact 7.2.1 not signed: https://api.github.com/repos/reactiveui/refit/releases/175785740","Warn: release artifact 7.2.0 not signed: https://api.github.com/repos/reactiveui/refit/releases/175246523","Warn: release artifact 7.2.22 does not have provenance: https://api.github.com/repos/reactiveui/refit/releases/184364834","Warn: release artifact 8.0.0 does not have provenance: https://api.github.com/repos/reactiveui/refit/releases/183236773","Warn: release artifact 7.2.1 does not have provenance: https://api.github.com/repos/reactiveui/refit/releases/175785740","Warn: release artifact 7.2.0 does not have provenance: https://api.github.com/repos/reactiveui/refit/releases/175246523"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-18T10:19:14.568Z","repository_id":9654340,"created_at":"2025-08-18T10:19:14.568Z","updated_at":"2025-08-18T10:19:14.568Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274356900,"owners_count":25270566,"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","status":"online","status_checked_at":"2025-09-09T02:00:10.223Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["c-sharp","dotnet","dotnet-core","http","json","xamarin","xml"],"created_at":"2024-07-31T02:00:41.191Z","updated_at":"2026-02-06T03:04:11.480Z","avatar_url":"https://github.com/reactiveui.png","language":"C#","readme":"![Refit](refit_logo.png)\n\n## Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET\n\n[![Build](https://github.com/reactiveui/refit/actions/workflows/ci-build.yml/badge.svg)](https://github.com/reactiveui/refit/actions/workflows/ci-build.yml) [![codecov](https://codecov.io/github/reactiveui/refit/branch/main/graph/badge.svg?token=2guEgHsDU2)](https://codecov.io/github/reactiveui/refit)\n\n||Refit|Refit.HttpClientFactory|Refit.Newtonsoft.Json|\n|-|-|-|-|\n|*NuGet*|[![NuGet](https://img.shields.io/nuget/v/Refit.svg)](https://www.nuget.org/packages/Refit/)|[![NuGet](https://img.shields.io/nuget/v/Refit.HttpClientFactory.svg)](https://www.nuget.org/packages/Refit.HttpClientFactory/)|[![NuGet](https://img.shields.io/nuget/v/Refit.Newtonsoft.Json.svg)](https://www.nuget.org/packages/Refit.Newtonsoft.Json/)|\n\nRefit is a library heavily inspired by Square's\n[Retrofit](http://square.github.io/retrofit) library, and it turns your REST\nAPI into a live interface:\n\n```csharp\npublic interface IGitHubApi\n{\n    [Get(\"/users/{user}\")]\n    Task\u003cUser\u003e GetUser(string user);\n}\n```\n\nThe `RestService` class generates an implementation of `IGitHubApi` that uses\n`HttpClient` to make its calls:\n\n```csharp\nvar gitHubApi = RestService.For\u003cIGitHubApi\u003e(\"https://api.github.com\");\nvar octocat = await gitHubApi.GetUser(\"octocat\");\n```\n.NET Core supports registering via HttpClientFactory\n```csharp\nservices\n    .AddRefitClient\u003cIGitHubApi\u003e()\n    .ConfigureHttpClient(c =\u003e c.BaseAddress = new Uri(\"https://api.github.com\"));\n```\n\n# Table of Contents\n\n* [Where does this work?](#where-does-this-work)\n  * [Breaking changes in 6.x](#breaking-changes-in-6x)\n* [API Attributes](#api-attributes)\n* [Querystrings](#querystrings)\n  * [Dynamic Querystring Parameters](#dynamic-querystring-parameters)\n  * [Collections as Querystring parameters](#collections-as-querystring-parameters)\n  * [Unescape Querystring parameters](#unescape-querystring-parameters)\n  * [Custom Querystring Parameter formatting](#custom-querystring-parameter-formatting)\n* [Body content](#body-content)\n  * [Buffering and the Content-Length header](#buffering-and-the-content-length-header)\n  * [JSON content](#json-content)\n  * [XML Content](#xml-content)\n  * [Form posts](#form-posts)\n* [Setting request headers](#setting-request-headers)\n  * [Static headers](#static-headers)\n  * [Dynamic headers](#dynamic-headers)\n  * [Bearer Authentication](#bearer-authentication)\n  * [Reducing header boilerplate with DelegatingHandlers (Authorization headers worked example)](#reducing-header-boilerplate-with-delegatinghandlers-authorization-headers-worked-example)\n  * [Redefining headers](#redefining-headers)\n  * [Removing headers](#removing-headers)\n* [Passing state into DelegatingHandlers](#passing-state-into-delegatinghandlers)\n  * [Support for Polly and Polly.Context](#support-for-polly-and-pollycontext)\n  * [Target Interface type](#target-interface-type)\n  * [MethodInfo of the method on the Refit client interface that was invoked](#methodinfo-of-the-method-on-the-refit-client-interface-that-was-invoked)\n* [Multipart uploads](#multipart-uploads)\n* [Retrieving the response](#retrieving-the-response)\n* [Using generic interfaces](#using-generic-interfaces)\n* [Interface inheritance](#interface-inheritance)\n  * [Headers inheritance](#headers-inheritance)\n* [Default Interface Methods](#default-interface-methods)\n* [Using HttpClientFactory](#using-httpclientfactory)\n* [Providing a custom HttpClient](#providing-a-custom-httpclient)\n* [Handling exceptions](#handling-exceptions)\n  * [When returning Task\u0026lt;IApiResponse\u0026gt;, Task\u0026lt;IApiResponse\u0026lt;T\u0026gt;\u0026gt;, or Task\u0026lt;ApiResponse\u0026lt;T\u0026gt;\u0026gt;](#when-returning-taskiapiresponse-taskiapiresponset-or-taskapiresponset)\n  * [When returning Task\u0026lt;T\u0026gt;](#when-returning-taskt)\n  * [Providing a custom ExceptionFactory](#providing-a-custom-exceptionfactory)\n  * [ApiException deconstruction with Serilog](#apiexception-deconstruction-with-serilog)\n\n### Where does this work?\n\nRefit currently supports the following platforms and any .NET Standard 2.0 target:\n\n* UWP\n* Xamarin.Android\n* Xamarin.Mac\n* Xamarin.iOS\n* Desktop .NET 4.6.1\n* .NET 6 / 8\n* Blazor\n* Uno Platform\n\n### SDK Requirements\n\n### Updates in 8.0.x\nFixes for some issues experienced, this lead to some breaking changes.\nSee [Releases](https://github.com/reactiveui/refit/releases) for full details.\n\n### V6.x.x\n\nRefit 6 requires Visual Studio 16.8 or higher, or the .NET SDK 5.0.100 or higher. It can target any .NET Standard 2.0 platform.\n\nRefit 6 does not support the old `packages.config` format for NuGet references (as they do not support analyzers/source generators). You must\n[migrate to PackageReference](https://devblogs.microsoft.com/nuget/migrate-packages-config-to-package-reference/) to use Refit v6 and later.\n\n#### Breaking changes in 6.x\n\nRefit 6 makes [System.Text.Json](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-overview) the default JSON serializer. If you'd like to continue to use `Newtonsoft.Json`, add the `Refit.Newtonsoft.Json` NuGet package and set your `ContentSerializer` to `NewtonsoftJsonContentSerializer` on your `RefitSettings` instance. `System.Text.Json` is faster and uses less memory, though not all features are supported. The [migration guide](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0) contains more details.\n\n`IContentSerializer` was renamed to `IHttpContentSerializer` to better reflect its purpose. Additionally, two of its methods were renamed, `SerializeAsync\u003cT\u003e` -\u003e `ToHttpContent\u003cT\u003e` and `DeserializeAsync\u003cT\u003e` -\u003e `FromHttpContentAsync\u003cT\u003e`. Any existing implementations of these will need to be updated, though the changes should be minor.\n\n##### Updates in 6.3\n\nRefit 6.3 splits out the XML serialization via `XmlContentSerializer` into a separate package, `Refit.Xml`. This\nis to reduce the dependency size when using Refit with Web Assembly (WASM) applications. If you require XML, add a reference\nto `Refit.Xml`.\n\n### API Attributes\n\nEvery method must have an HTTP attribute that provides the request method and\nrelative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and\nHead. The relative URL of the resource is specified in the annotation.\n\n```csharp\n[Get(\"/users/list\")]\n```\n\nYou can also specify query parameters in the URL:\n\n```csharp\n[Get(\"/users/list?sort=desc\")]\n```\n\nA request URL can be updated dynamically using replacement blocks and\nparameters on the method. A replacement block is an alphanumeric string\nsurrounded by { and }.\n\nIf the name of your parameter doesn't match the name in the URL path, use the\n`AliasAs` attribute.\n\n```csharp\n[Get(\"/group/{id}/users\")]\nTask\u003cList\u003cUser\u003e\u003e GroupList([AliasAs(\"id\")] int groupId);\n```\n\nA request url can also bind replacement blocks to a custom object\n\n```csharp\n[Get(\"/group/{request.groupId}/users/{request.userId}\")]\nTask\u003cList\u003cUser\u003e\u003e GroupList(UserGroupRequest request);\n\nclass UserGroupRequest{\n    int groupId { get;set; }\n    int userId { get;set; }\n}\n\n```\n\nParameters that are not specified as a URL substitution will automatically be\nused as query parameters. This is different than Retrofit, where all\nparameters must be explicitly specified.\n\nThe comparison between parameter name and URL parameter is *not*\ncase-sensitive, so it will work correctly if you name your parameter `groupId`\nin the path `/group/{groupid}/show` for example.\n\n```csharp\n[Get(\"/group/{groupid}/users\")]\nTask\u003cList\u003cUser\u003e\u003e GroupList(int groupId, [AliasAs(\"sort\")] string sortOrder);\n\nGroupList(4, \"desc\");\n\u003e\u003e\u003e \"/group/4/users?sort=desc\"\n```\n\nRound-tripping route parameter syntax: Forward slashes aren't encoded when using a double-asterisk (\\*\\*) catch-all parameter syntax.\n\nDuring link generation, the routing system encodes the value captured in a double-asterisk (\\*\\*) catch-all parameter (for example, {**myparametername}) except the forward slashes.\n\nThe type of round-tripping route parameter must be string.\n\n```csharp\n[Get(\"/search/{**page}\")]\nTask\u003cList\u003cPage\u003e\u003e Search(string page);\n\nSearch(\"admin/products\");\n\u003e\u003e\u003e \"/search/admin/products\"\n```\n\n### Querystrings\n\n#### Dynamic Querystring Parameters\n\nIf you specify an `object` as a query parameter, all public properties which are not null are used as query parameters.\nThis previously only applied to GET requests, but has now been expanded to all HTTP request methods, partly thanks to Twitter's hybrid API that insists on non-GET requests with querystring parameters.\nUse the `Query` attribute to change the behavior to 'flatten' your query parameter object. If using this Attribute you can specify values for the Delimiter and the Prefix which are used to 'flatten' the object.\n\n```csharp\npublic class MyQueryParams\n{\n    [AliasAs(\"order\")]\n    public string SortOrder { get; set; }\n\n    public int Limit { get; set; }\n\n    public KindOptions Kind { get; set; }\n}\n\npublic enum KindOptions\n{\n    Foo,\n\n    [EnumMember(Value = \"bar\")]\n    Bar\n}\n\n\n[Get(\"/group/{id}/users\")]\nTask\u003cList\u003cUser\u003e\u003e GroupList([AliasAs(\"id\")] int groupId, MyQueryParams params);\n\n[Get(\"/group/{id}/users\")]\nTask\u003cList\u003cUser\u003e\u003e GroupListWithAttribute([AliasAs(\"id\")] int groupId, [Query(\".\",\"search\")] MyQueryParams params);\n\n\nparams.SortOrder = \"desc\";\nparams.Limit = 10;\nparams.Kind = KindOptions.Bar;\n\nGroupList(4, params)\n\u003e\u003e\u003e \"/group/4/users?order=desc\u0026Limit=10\u0026Kind=bar\"\n\nGroupListWithAttribute(4, params)\n\u003e\u003e\u003e \"/group/4/users?search.order=desc\u0026search.Limit=10\u0026search.Kind=bar\"\n```\n\nA similar behavior exists if using a Dictionary, but without the advantages of the `AliasAs` attributes and of course no intellisense and/or type safety.\n\nYou can also specify querystring parameters with [Query] and have them flattened in non-GET requests, similar to:\n```csharp\n[Post(\"/statuses/update.json\")]\nTask\u003cTweet\u003e PostTweet([Query]TweetParams params);\n```\n\nWhere `TweetParams` is a POCO, and properties will also support `[AliasAs]` attributes.\n\n#### Collections as Querystring parameters\n\nUse the `Query` attribute to specify format in which collections should be formatted in query string\n\n```csharp\n[Get(\"/users/list\")]\nTask Search([Query(CollectionFormat.Multi)]int[] ages);\n\nSearch(new [] {10, 20, 30})\n\u003e\u003e\u003e \"/users/list?ages=10\u0026ages=20\u0026ages=30\"\n\n[Get(\"/users/list\")]\nTask Search([Query(CollectionFormat.Csv)]int[] ages);\n\nSearch(new [] {10, 20, 30})\n\u003e\u003e\u003e \"/users/list?ages=10%2C20%2C30\"\n```\n\nYou can also specify collection format in `RefitSettings`, that will be used by default, unless explicitly defined in `Query` attribute.\n\n```csharp\nvar gitHubApi = RestService.For\u003cIGitHubApi\u003e(\"https://api.github.com\",\n    new RefitSettings {\n        CollectionFormat = CollectionFormat.Multi\n    });\n```\n\n#### Unescape Querystring parameters\n\nUse the `QueryUriFormat` attribute to specify if the query parameters should be url escaped\n\n```csharp\n[Get(\"/query\")]\n[QueryUriFormat(UriFormat.Unescaped)]\nTask Query(string q);\n\nQuery(\"Select+Id,Name+From+Account\")\n\u003e\u003e\u003e \"/query?q=Select+Id,Name+From+Account\"\n```\n\n#### Custom Querystring parameter formatting\n\n**Formatting Keys**\n\nTo customize the format of query keys, you have two main options:\n\n1. **Using the `AliasAs` Attribute**:\n\n   You can use the `AliasAs` attribute to specify a custom key name for a property. This attribute will always take precedence over any key formatter you specify.\n\n   ```csharp\n   public class MyQueryParams\n   {\n       [AliasAs(\"order\")]\n       public string SortOrder { get; set; }\n\n       public int Limit { get; set; }\n   }\n\n   [Get(\"/group/{id}/users\")]\n   Task\u003cList\u003cUser\u003e\u003e GroupList([AliasAs(\"id\")] int groupId, [Query] MyQueryParams params);\n\n   params.SortOrder = \"desc\";\n   params.Limit = 10;\n\n   GroupList(1, params);\n   ```\n\n   This will generate the following request:\n\n   ```\n   /group/1/users?order=desc\u0026Limit=10\n   ```\n\n2. **Using the `RefitSettings.UrlParameterKeyFormatter` Property**:\n\n   By default, Refit uses the property name as the query key without any additional formatting. If you want to apply a custom format across all your query keys, you can use the `UrlParameterKeyFormatter` property. Remember that if a property has an `AliasAs` attribute, it will be used regardless of the formatter.\n\n   The following example uses the built-in `CamelCaseUrlParameterKeyFormatter`:\n\n   ```csharp\n   public class MyQueryParams\n   {\n       public string SortOrder { get; set; }\n\n       [AliasAs(\"queryLimit\")]\n       public int Limit { get; set; }\n   }\n\n   [Get(\"/group/users\")]\n   Task\u003cList\u003cUser\u003e\u003e GroupList([Query] MyQueryParams params);\n\n   params.SortOrder = \"desc\";\n   params.Limit = 10;\n   ```\n\n   The request will look like:\n\n   ```\n   /group/users?sortOrder=desc\u0026queryLimit=10\n   ```\n\n**Note**: The `AliasAs` attribute always takes the top priority. If both the attribute and a custom key formatter are present, the `AliasAs` attribute's value will be used.\n\n#### Formatting URL Parameter Values with the `UrlParameterFormatter`\n\nIn Refit, the `UrlParameterFormatter` property within `RefitSettings` allows you to customize how parameter values are formatted in the URL. This can be particularly useful when you need to format dates, numbers, or other types in a specific manner that aligns with your API's expectations.\n\n**Using `UrlParameterFormatter`**:\n\nAssign a custom formatter that implements the `IUrlParameterFormatter` interface to the `UrlParameterFormatter` property.\n\n```csharp\npublic class CustomDateUrlParameterFormatter : IUrlParameterFormatter\n{\n    public string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type)\n    {\n        if (value is DateTime dt)\n        {\n            return dt.ToString(\"yyyyMMdd\");\n        }\n\n        return value?.ToString();\n    }\n}\n\nvar settings = new RefitSettings\n{\n    UrlParameterFormatter = new CustomDateUrlParameterFormatter()\n};\n```\n\nIn this example, a custom formatter is created for date values. Whenever a `DateTime` parameter is encountered, it formats the date as `yyyyMMdd`.\n\n**Formatting Dictionary Keys**:\n\nWhen dealing with dictionaries, it's important to note that keys are treated as values. If you need custom formatting for dictionary keys, you should use the `UrlParameterFormatter` as well.\n\nFor instance, if you have a dictionary parameter and you want to format its keys in a specific way, you can handle that in the custom formatter:\n\n```csharp\npublic class CustomDictionaryKeyFormatter : IUrlParameterFormatter\n{\n    public string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type)\n    {\n        // Handle dictionary keys\n        if (attributeProvider is PropertyInfo prop \u0026\u0026 prop.PropertyType.IsGenericType \u0026\u0026 prop.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary\u003c,\u003e))\n        {\n            // Custom formatting logic for dictionary keys\n            return value?.ToString().ToUpperInvariant();\n        }\n\n        return value?.ToString();\n    }\n}\n\nvar settings = new RefitSettings\n{\n    UrlParameterFormatter = new CustomDictionaryKeyFormatter()\n};\n```\n\nIn the above example, the dictionary keys will be converted to uppercase.\n\n### Body content\n\nOne of the parameters in your method can be used as the body, by using the\nBody attribute:\n\n```csharp\n[Post(\"/users/new\")]\nTask CreateUser([Body] User user);\n```\n\nThere are four possibilities for supplying the body data, depending on the\ntype of the parameter:\n\n* If the type is `Stream`, the content will be streamed via `StreamContent`\n* If the type is `string`, the string will be used directly as the content unless `[Body(BodySerializationMethod.Json)]` is set which will send it as a `StringContent`\n* If the parameter has the attribute `[Body(BodySerializationMethod.UrlEncoded)]`,\n  the content will be URL-encoded (see [form posts](#form-posts) below)\n* For all other types, the object will be serialized using the content serializer specified in\nRefitSettings (JSON is the default).\n\n#### Buffering and the `Content-Length` header\n\nBy default, Refit streams the body content without buffering it. This means you can\nstream a file from disk, for example, without incurring the overhead of loading\nthe whole file into memory. The downside of this is that no `Content-Length` header\nis set _on the request_. If your API needs you to send a `Content-Length` header with\nthe request, you can disable this streaming behavior by setting the `buffered` argument\nof the `[Body]` attribute to `true`:\n\n```csharp\nTask CreateUser([Body(buffered: true)] User user);\n```\n\n#### JSON content\n\nJSON requests and responses are serialized/deserialized using an instance of the `IHttpContentSerializer` interface. Refit provides two implementations out of the box: `SystemTextJsonContentSerializer` (which is the default JSON serializer) and `NewtonsoftJsonContentSerializer`. The first uses `System.Text.Json` APIs and is focused on high performance and low memory usage, while the latter uses the known `Newtonsoft.Json` library and is more versatile and customizable. You can read more about the two serializers and the main differences between the two [at this link](https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to).\n\nFor instance, here is how to create a new `RefitSettings` instance using the `Newtonsoft.Json`-based serializer (you'll also need to add a `PackageReference` to `Refit.Newtonsoft.Json`):\n\n```csharp\nvar settings = new RefitSettings(new NewtonsoftJsonContentSerializer());\n```\n\nIf you're using `Newtonsoft.Json` APIs, you can customize their behavior by setting the `Newtonsoft.Json.JsonConvert.DefaultSettings` property:\n\n```csharp\nJsonConvert.DefaultSettings =\n    () =\u003e new JsonSerializerSettings() {\n        ContractResolver = new CamelCasePropertyNamesContractResolver(),\n        Converters = {new StringEnumConverter()}\n    };\n\n// Serialized as: {\"day\":\"Saturday\"}\nawait PostSomeStuff(new { Day = DayOfWeek.Saturday });\n```\n\nAs these are global settings they will affect your entire application. It\nmight be beneficial to isolate the settings for calls to a particular API.\nWhen creating a Refit generated live interface, you may optionally pass a\n`RefitSettings` that will allow you to specify what serializer settings you\nwould like. This allows you to have different serializer settings for separate\nAPIs:\n\n```csharp\nvar gitHubApi = RestService.For\u003cIGitHubApi\u003e(\"https://api.github.com\",\n    new RefitSettings {\n        ContentSerializer = new NewtonsoftJsonContentSerializer(\n            new JsonSerializerSettings {\n                ContractResolver = new SnakeCasePropertyNamesContractResolver()\n        }\n    )});\n\nvar otherApi = RestService.For\u003cIOtherApi\u003e(\"https://api.example.com\",\n    new RefitSettings {\n        ContentSerializer = new NewtonsoftJsonContentSerializer(\n            new JsonSerializerSettings {\n                ContractResolver = new CamelCasePropertyNamesContractResolver()\n        }\n    )});\n```\n\nProperty serialization/deserialization can be customised using Json.NET's\nJsonProperty attribute:\n\n```csharp\npublic class Foo\n{\n    // Works like [AliasAs(\"b\")] would in form posts (see below)\n    [JsonProperty(PropertyName=\"b\")]\n    public string Bar { get; set; }\n}\n```\n\n##### JSON source generator\n\nTo apply the benefits of the new [JSON source generator](https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/) for System.Text.Json added in .NET 6, you can use `SystemTextJsonContentSerializer` with a custom instance of `RefitSettings` and `JsonSerializerOptions`:\n\n```csharp\nvar gitHubApi = RestService.For\u003cIGitHubApi\u003e(\"https://api.github.com\",\n    new RefitSettings {\n        ContentSerializer = new SystemTextJsonContentSerializer(MyJsonSerializerContext.Default.Options)\n    });\n```\n\n#### XML Content\n\nXML requests and responses are serialized/deserialized using _System.Xml.Serialization.XmlSerializer_.\nBy default, Refit will use JSON content serialization, to use XML content configure the ContentSerializer to use the `XmlContentSerializer`:\n\n```csharp\nvar gitHubApi = RestService.For\u003cIXmlApi\u003e(\"https://www.w3.org/XML\",\n    new RefitSettings {\n        ContentSerializer = new XmlContentSerializer()\n    });\n```\n\nProperty serialization/deserialization can be customised using   attributes found in the _System.Xml.Serialization_ namespace:\n\n```csharp\n    public class Foo\n    {\n        [XmlElement(Namespace = \"https://www.w3.org/XML\")]\n        public string Bar { get; set; }\n    }\n```\n\nThe _System.Xml.Serialization.XmlSerializer_ provides many options for serializing, those options can be set by providing an `XmlContentSerializerSettings` to the `XmlContentSerializer` constructor:\n\n```csharp\nvar gitHubApi = RestService.For\u003cIXmlApi\u003e(\"https://www.w3.org/XML\",\n    new RefitSettings {\n        ContentSerializer = new XmlContentSerializer(\n            new XmlContentSerializerSettings\n            {\n                XmlReaderWriterSettings = new XmlReaderWriterSettings()\n                {\n                    ReaderSettings = new XmlReaderSettings\n                    {\n                        IgnoreWhitespace = true\n                    }\n                }\n            }\n        )\n    });\n```\n\n#### \u003ca name=\"form-posts\"\u003e\u003c/a\u003eForm posts\n\nFor APIs that take form posts (i.e. serialized as `application/x-www-form-urlencoded`),\ninitialize the Body attribute with `BodySerializationMethod.UrlEncoded`.\n\nThe parameter can be an `IDictionary`:\n\n```csharp\npublic interface IMeasurementProtocolApi\n{\n    [Post(\"/collect\")]\n    Task Collect([Body(BodySerializationMethod.UrlEncoded)] Dictionary\u003cstring, object\u003e data);\n}\n\nvar data = new Dictionary\u003cstring, object\u003e {\n    {\"v\", 1},\n    {\"tid\", \"UA-1234-5\"},\n    {\"cid\", new Guid(\"d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c\")},\n    {\"t\", \"event\"},\n};\n\n// Serialized as: v=1\u0026tid=UA-1234-5\u0026cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c\u0026t=event\nawait api.Collect(data);\n```\n\nOr you can just pass any object and all _public, readable_ properties will\nbe serialized as form fields in the request. This approach allows you to alias\nproperty names using `[AliasAs(\"whatever\")]` which can help if the API has\ncryptic field names:\n\n```csharp\npublic interface IMeasurementProtocolApi\n{\n    [Post(\"/collect\")]\n    Task Collect([Body(BodySerializationMethod.UrlEncoded)] Measurement measurement);\n}\n\npublic class Measurement\n{\n    // Properties can be read-only and [AliasAs] isn't required\n    public int v { get { return 1; } }\n\n    [AliasAs(\"tid\")]\n    public string WebPropertyId { get; set; }\n\n    [AliasAs(\"cid\")]\n    public Guid ClientId { get; set; }\n\n    [AliasAs(\"t\")]\n    public string Type { get; set; }\n\n    public object IgnoreMe { private get; set; }\n}\n\nvar measurement = new Measurement {\n    WebPropertyId = \"UA-1234-5\",\n    ClientId = new Guid(\"d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c\"),\n    Type = \"event\"\n};\n\n// Serialized as: v=1\u0026tid=UA-1234-5\u0026cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c\u0026t=event\nawait api.Collect(measurement);\n```\n\nIf you have a type that has `[JsonProperty(PropertyName)]` attributes setting property aliases, Refit will use those too (`[AliasAs]` will take precedence where you have both).\nThis means that the following type will serialize as `one=value1\u0026two=value2`:\n\n```csharp\n\npublic class SomeObject\n{\n    [JsonProperty(PropertyName = \"one\")]\n    public string FirstProperty { get; set; }\n\n    [JsonProperty(PropertyName = \"notTwo\")]\n    [AliasAs(\"two\")]\n    public string SecondProperty { get; set; }\n}\n\n```\n\n**NOTE:** This use of `AliasAs` applies to querystring parameters and form body posts, but not to response objects; for aliasing fields on response objects, you'll still need to use `[JsonProperty(\"full-property-name\")]`.\n\n### Setting request headers\n\n#### Static headers\n\nYou can set one or more static request headers for a request applying a `Headers`\nattribute to the method:\n\n```csharp\n[Headers(\"User-Agent: Awesome Octocat App\")]\n[Get(\"/users/{user}\")]\nTask\u003cUser\u003e GetUser(string user);\n```\n\nStatic headers can also be added to _every request in the API_ by applying the\n`Headers` attribute to the interface:\n\n```csharp\n[Headers(\"User-Agent: Awesome Octocat App\")]\npublic interface IGitHubApi\n{\n    [Get(\"/users/{user}\")]\n    Task\u003cUser\u003e GetUser(string user);\n\n    [Post(\"/users/new\")]\n    Task CreateUser([Body] User user);\n}\n```\n\n#### Dynamic headers\n\nIf the content of the header needs to be set at runtime, you can add a header\nwith a dynamic value to a request by applying a `Header` attribute to a parameter:\n\n```csharp\n[Get(\"/users/{user}\")]\nTask\u003cUser\u003e GetUser(string user, [Header(\"Authorization\")] string authorization);\n\n// Will add the header \"Authorization: token OAUTH-TOKEN\" to the request\nvar user = await GetUser(\"octocat\", \"token OAUTH-TOKEN\");\n```\n\nAdding an `Authorization` header is such a common use case that you can add an access token to a request by applying an `Authorize` attribute to a parameter and optionally specifying the scheme:\n\n```csharp\n[Get(\"/users/{user}\")]\nTask\u003cUser\u003e GetUser(string user, [Authorize(\"Bearer\")] string token);\n\n// Will add the header \"Authorization: Bearer OAUTH-TOKEN}\" to the request\nvar user = await GetUser(\"octocat\", \"OAUTH-TOKEN\");\n\n//note: the scheme defaults to Bearer if none provided\n```\n\nIf you need to set multiple headers at runtime, you can add a `IDictionary\u003cstring, string\u003e`\nand apply a `HeaderCollection` attribute to the parameter and it will inject the headers into the request:\n\n[//]: # ({% raw %})\n```csharp\n\n[Get(\"/users/{user}\")]\nTask\u003cUser\u003e GetUser(string user, [HeaderCollection] IDictionary\u003cstring, string\u003e headers);\n\nvar headers = new Dictionary\u003cstring, string\u003e {{\"Authorization\",\"Bearer tokenGoesHere\"}, {\"X-Tenant-Id\",\"123\"}};\nvar user = await GetUser(\"octocat\", headers);\n```\n[//]: # ({% endraw %})\n\n#### Bearer Authentication\n\nMost APIs need some sort of Authentication. The most common is OAuth Bearer authentication. A header is added to each request of the form: `Authorization: Bearer \u003ctoken\u003e`. Refit makes it easy to insert your logic to get the token however your app needs, so you don't have to pass a token into each method.\n\n1. Add `[Headers(\"Authorization: Bearer\")]` to the interface or methods which need the token.\n2. Set `AuthorizationHeaderValueGetter` in the `RefitSettings` instance. Refit will call your delegate each time it needs to obtain the token, so it's a good idea for your mechanism to cache the token value for some period within the token lifetime.\n\n#### Reducing header boilerplate with DelegatingHandlers (Authorization headers worked example)\nAlthough we make provisions for adding dynamic headers at runtime directly in Refit,\nmost use-cases would likely benefit from registering a custom `DelegatingHandler` in order to inject the headers as part of the `HttpClient` middleware pipeline\nthus removing the need to add lots of `[Header]` or `[HeaderCollection]` attributes.\n\nIn the example above we are leveraging a `[HeaderCollection]` parameter to inject an `Authorization` and `X-Tenant-Id` header.\nThis is quite a common scenario if you are integrating with a 3rd party that uses OAuth2. While it's ok for the occasional endpoint,\nit would be quite cumbersome if we had to add that boilerplate to every method in our interface.\n\nIn this example we will assume our application is a multi-tenant application that is able to pull information about a tenant through\nsome interface `ITenantProvider` and has a data store `IAuthTokenStore` that can be used to retrieve an auth token to attach to the outbound request.\n\n```csharp\n\n //Custom delegating handler for adding Auth headers to outbound requests\n class AuthHeaderHandler : DelegatingHandler\n {\n     private readonly ITenantProvider tenantProvider;\n     private readonly IAuthTokenStore authTokenStore;\n\n    public AuthHeaderHandler(ITenantProvider tenantProvider, IAuthTokenStore authTokenStore)\n    {\n         this.tenantProvider = tenantProvider ?? throw new ArgumentNullException(nameof(tenantProvider));\n         this.authTokenStore = authTokenStore ?? throw new ArgumentNullException(nameof(authTokenStore));\n         // InnerHandler must be left as null when using DI, but must be assigned a value when\n         // using RestService.For\u003cIMyApi\u003e\n         // InnerHandler = new HttpClientHandler();\n    }\n\n    protected override async Task\u003cHttpResponseMessage\u003e SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n    {\n        var token = await authTokenStore.GetToken();\n\n        //potentially refresh token here if it has expired etc.\n\n        request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", token);\n        request.Headers.Add(\"X-Tenant-Id\", tenantProvider.GetTenantId());\n\n        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);\n    }\n}\n\n//Startup.cs\npublic void ConfigureServices(IServiceCollection services)\n{\n    services.AddTransient\u003cITenantProvider, TenantProvider\u003e();\n    services.AddTransient\u003cIAuthTokenStore, AuthTokenStore\u003e();\n    services.AddTransient\u003cAuthHeaderHandler\u003e();\n\n    //this will add our refit api implementation with an HttpClient\n    //that is configured to add auth headers to all requests\n\n    //note: AddRefitClient\u003cT\u003e requires a reference to Refit.HttpClientFactory\n    //note: the order of delegating handlers is important and they run in the order they are added!\n\n    services.AddRefitClient\u003cISomeThirdPartyApi\u003e()\n        .ConfigureHttpClient(c =\u003e c.BaseAddress = new Uri(\"https://api.example.com\"))\n        .AddHttpMessageHandler\u003cAuthHeaderHandler\u003e();\n        //you could add Polly here to handle HTTP 429 / HTTP 503 etc\n}\n\n//Your application code\npublic class SomeImportantBusinessLogic\n{\n    private ISomeThirdPartyApi thirdPartyApi;\n\n    public SomeImportantBusinessLogic(ISomeThirdPartyApi thirdPartyApi)\n    {\n        this.thirdPartyApi = thirdPartyApi;\n    }\n\n    public async Task DoStuffWithUser(string username)\n    {\n        var user = await thirdPartyApi.GetUser(username);\n        //do your thing\n    }\n}\n```\n\nIf you aren't using dependency injection then you could achieve the same thing by doing something like this:\n\n```csharp\nvar api = RestService.For\u003cISomeThirdPartyApi\u003e(new HttpClient(new AuthHeaderHandler(tenantProvider, authTokenStore))\n    {\n        BaseAddress = new Uri(\"https://api.example.com\")\n    }\n);\n\nvar user = await thirdPartyApi.GetUser(username);\n//do your thing\n```\n\n#### Redefining headers\n\nUnlike Retrofit, where headers do not overwrite each other and are all added to\nthe request regardless of how many times the same header is defined, Refit takes\na similar approach to the approach ASP.NET MVC takes with action filters \u0026mdash;\n**redefining a header will replace it**, in the following order of precedence:\n\n* `Headers` attribute on the interface _(lowest priority)_\n* `Headers` attribute on the method\n* `Header` attribute or `HeaderCollection` attribute on a method parameter _(highest priority)_\n\n```csharp\n[Headers(\"X-Emoji: :rocket:\")]\npublic interface IGitHubApi\n{\n    [Get(\"/users/list\")]\n    Task\u003cList\u003e GetUsers();\n\n    [Get(\"/users/{user}\")]\n    [Headers(\"X-Emoji: :smile_cat:\")]\n    Task\u003cUser\u003e GetUser(string user);\n\n    [Post(\"/users/new\")]\n    [Headers(\"X-Emoji: :metal:\")]\n    Task CreateUser([Body] User user, [Header(\"X-Emoji\")] string emoji);\n}\n\n// X-Emoji: :rocket:\nvar users = await GetUsers();\n\n// X-Emoji: :smile_cat:\nvar user = await GetUser(\"octocat\");\n\n// X-Emoji: :trollface:\nawait CreateUser(user, \":trollface:\");\n```\n\n**Note:** This redefining behavior only applies to headers _with the same name_. Headers with different names are not replaced. The following code will result in all headers being included:\n\n```csharp\n[Headers(\"Header-A: 1\")]\npublic interface ISomeApi\n{\n    [Headers(\"Header-B: 2\")]\n    [Post(\"/post\")]\n    Task PostTheThing([Header(\"Header-C\")] int c);\n}\n\n// Header-A: 1\n// Header-B: 2\n// Header-C: 3\nvar user = await api.PostTheThing(3);\n```\n\n#### Removing headers\n\nHeaders defined on an interface or method can be removed by redefining\na static header without a value (i.e. without `: \u003cvalue\u003e`) or passing `null` for\na dynamic header. _Empty strings will be included as empty headers._\n\n```csharp\n[Headers(\"X-Emoji: :rocket:\")]\npublic interface IGitHubApi\n{\n    [Get(\"/users/list\")]\n    [Headers(\"X-Emoji\")] // Remove the X-Emoji header\n    Task\u003cList\u003e GetUsers();\n\n    [Get(\"/users/{user}\")]\n    [Headers(\"X-Emoji:\")] // Redefine the X-Emoji header as empty\n    Task\u003cUser\u003e GetUser(string user);\n\n    [Post(\"/users/new\")]\n    Task CreateUser([Body] User user, [Header(\"X-Emoji\")] string emoji);\n}\n\n// No X-Emoji header\nvar users = await GetUsers();\n\n// X-Emoji:\nvar user = await GetUser(\"octocat\");\n\n// No X-Emoji header\nawait CreateUser(user, null);\n\n// X-Emoji:\nawait CreateUser(user, \"\");\n```\n\n### Passing state into DelegatingHandlers\n\nIf there is runtime state that you need to pass to a `DelegatingHandler` you can add a property with a dynamic value to the underlying `HttpRequestMessage.Properties`\nby applying a `Property` attribute to a parameter:\n\n```csharp\npublic interface IGitHubApi\n{\n    [Post(\"/users/new\")]\n    Task CreateUser([Body] User user, [Property(\"SomeKey\")] string someValue);\n\n    [Post(\"/users/new\")]\n    Task CreateUser([Body] User user, [Property] string someOtherKey);\n}\n```\n\nThe attribute constructor optionally takes a string which becomes the key in the `HttpRequestMessage.Properties` dictionary.\nIf no key is explicitly defined then the name of the parameter becomes the key.\nIf a key is defined multiple times the value in `HttpRequestMessage.Properties` will be overwritten.\nThe parameter itself can be any `object`. Properties can be accessed inside a `DelegatingHandler` as follows:\n\n```csharp\nclass RequestPropertyHandler : DelegatingHandler\n{\n    public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}\n\n    protected override async Task\u003cHttpResponseMessage\u003e SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n    {\n        // See if the request has a the property\n        if(request.Properties.ContainsKey(\"SomeKey\"))\n        {\n            var someProperty = request.Properties[\"SomeKey\"];\n            //do stuff\n        }\n\n        if(request.Properties.ContainsKey(\"someOtherKey\"))\n        {\n            var someOtherProperty = request.Properties[\"someOtherKey\"];\n            //do stuff\n        }\n\n        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);\n    }\n}\n```\n\nNote: in .NET 5 `HttpRequestMessage.Properties` has been marked `Obsolete` and Refit will instead populate the value into the new `HttpRequestMessage.Options`.\n\n#### Support for Polly and Polly.Context\n\nBecause Refit supports `HttpClientFactory` it is possible to configure Polly policies on your HttpClient.\nIf your policy makes use of `Polly.Context` this can be passed via Refit by adding `[Property(\"PolicyExecutionContext\")] Polly.Context context`\nas behind the scenes `Polly.Context` is simply stored in `HttpRequestMessage.Properties` under the key `PolicyExecutionContext` and is of type `Polly.Context`. It's only recommended to pass the `Polly.Context` this way if your use case requires that the `Polly.Context` be initialized with dynamic content only known at runtime. If your `Polly.Context` only requires the same content every time (e.g an `ILogger` that you want to use to log from inside your policies) a cleaner approach is to inject the `Polly.Context` via a `DelegatingHandler` as described in [#801](https://github.com/reactiveui/refit/issues/801#issuecomment-1137318526)\n\n#### Target Interface Type and method info\n\nThere may be times when you want to know what the target interface type is of the Refit instance. An example is where you\nhave a derived interface that implements a common base like this:\n\n```csharp\npublic interface IGetAPI\u003cTEntity\u003e\n{\n    [Get(\"/{key}\")]\n    Task\u003cTEntity\u003e Get(long key);\n}\n\npublic interface IUsersAPI : IGetAPI\u003cUser\u003e\n{\n}\n\npublic interface IOrdersAPI : IGetAPI\u003cOrder\u003e\n{\n}\n```\n\nYou can access the concrete type of the interface for use in a handler, such as to alter the URL of the request:\n\n[//]: # ({% raw %})\n```csharp\nclass RequestPropertyHandler : DelegatingHandler\n{\n    public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}\n\n    protected override async Task\u003cHttpResponseMessage\u003e SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n    {\n        // Get the type of the target interface\n        Type interfaceType = (Type)request.Properties[HttpMessageRequestOptions.InterfaceType];\n\n        var builder = new UriBuilder(request.RequestUri);\n        // Alter the Path in some way based on the interface or an attribute on it\n        builder.Path = $\"/{interfaceType.Name}{builder.Path}\";\n        // Set the new Uri on the outgoing message\n        request.RequestUri = builder.Uri;\n\n        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);\n    }\n}\n```\n[//]: # ({% endraw %})\n\nThe full method information (`RestMethodInfo`) is also always available in the request options. The `RestMethodInfo` contains more information about the method being called such as the full `MethodInfo` when using reflection is needed:\n\n[//]: # ({% raw %})\n```csharp\nclass RequestPropertyHandler : DelegatingHandler\n{\n    public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}\n\n    protected override async Task\u003cHttpResponseMessage\u003e SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n    {\n        // Get the method info\n        if (request.Options.TryGetValue(new HttpRequestOptionsKey\u003cRestMethodInfo\u003e(HttpRequestMessageOptions.RestMethodInfo), out RestMethodInfo restMethodInfo))\n        {\n            var builder = new UriBuilder(request.RequestUri);\n            // Alter the Path in some way based on the method info or an attribute on it\n            builder.Path = $\"/{restMethodInfo.MethodInfo.Name}{builder.Path}\";\n            // Set the new Uri on the outgoing message\n            request.RequestUri = builder.Uri;\n        }\n\n        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);\n    }\n}\n```\n[//]: # ({% endraw %})\n\nNote: in .NET 5 `HttpRequestMessage.Properties` has been marked `Obsolete` and Refit will instead populate the value into the new `HttpRequestMessage.Options`. Refit provides `HttpRequestMessageOptions.InterfaceType` and `HttpRequestMessageOptions.RestMethodInfo` to respectively access the interface type and REST method info from the options.\n\n### Multipart uploads\n\nMethods decorated with `Multipart` attribute will be submitted with multipart content type.\nAt this time, multipart methods support the following parameter types:\n\n - `string` (parameter name will be used as name and string value as value)\n - byte array\n - `Stream`\n - `FileInfo`\n\nName of the field in the multipart data priority precedence:\n\n* `multipartItem.Name` if specified and not null (optional); dynamic, allows naming form data part at execution time.\n* `[AliasAs]` attribute  (optional) that decorate the streamPart parameter in the method signature (see below); static, defined in code.\n* `MultipartItem` parameter name (default) as defined in the method signature; static, defined in code.\n\nA custom boundary can be specified with an optional string parameter to the `Multipart` attribute. If left empty, this defaults to `----MyGreatBoundary`.\n\nTo specify the file name and content type for byte array (`byte[]`), `Stream` and `FileInfo` parameters, use of a wrapper class is required.\nThe wrapper classes for these types are `ByteArrayPart`, `StreamPart` and `FileInfoPart`.\n\n```csharp\npublic interface ISomeApi\n{\n    [Multipart]\n    [Post(\"/users/{id}/photo\")]\n    Task UploadPhoto(int id, [AliasAs(\"myPhoto\")] StreamPart stream);\n}\n```\n\nTo pass a `Stream` to this method, construct a StreamPart object like so:\n\n```csharp\nsomeApiInstance.UploadPhoto(id, new StreamPart(myPhotoStream, \"photo.jpg\", \"image/jpeg\"));\n```\n\nNote: The `AttachmentName` attribute that was previously described in this section has been deprecated and its use is not recommended.\n\n### Retrieving the response\n\nNote that in Refit unlike in Retrofit, there is no option for a synchronous\nnetwork request - all requests must be async, either via `Task` or via\n`IObservable`. There is also no option to create an async method via a Callback\nparameter unlike Retrofit, because we live in the async/await future.\n\nSimilarly to how body content changes via the parameter type, the return type\nwill determine the content returned.\n\nReturning Task without a type parameter will discard the content and solely\ntell you whether or not the call succeeded:\n\n```csharp\n[Post(\"/users/new\")]\nTask CreateUser([Body] User user);\n\n// This will throw if the network call fails\nawait CreateUser(someUser);\n```\n\nIf the type parameter is 'HttpResponseMessage' or 'string', the raw response\nmessage or the content as a string will be returned respectively.\n\n```csharp\n// Returns the content as a string (i.e. the JSON data)\n[Get(\"/users/{user}\")]\nTask\u003cstring\u003e GetUser(string user);\n\n// Returns the raw response, as an IObservable that can be used with the\n// Reactive Extensions\n[Get(\"/users/{user}\")]\nIObservable\u003cHttpResponseMessage\u003e GetUser(string user);\n```\n\nThere is also a generic wrapper class called `ApiResponse\u003cT\u003e` that can be used as a return type. Using this class as a return type allows you to retrieve not just the content as an object, but also any metadata associated with the request/response. This includes information such as response headers, the http status code and reason phrase (e.g. 404 Not Found), the response version, the original request message that was sent and in the case of an error, an `ApiException` object containing details of the error. Following are some examples of how you can retrieve the response metadata.\n\n```csharp\n//Returns the content within a wrapper class containing metadata about the request/response\n[Get(\"/users/{user}\")]\nTask\u003cApiResponse\u003cUser\u003e\u003e GetUser(string user);\n\n//Calling the API\nvar response = await gitHubApi.GetUser(\"octocat\");\n\n//Getting the status code (returns a value from the System.Net.HttpStatusCode enumeration)\nvar httpStatus = response.StatusCode;\n\n//Determining if a success status code was received and there wasn't any other error\n//(for example, during content deserialization)\nif(response.IsSuccessful)\n{\n    //YAY! Do the thing...\n}\n\n//Retrieving a well-known header value (e.g. \"Server\" header)\nvar serverHeaderValue = response.Headers.Server != null ? response.Headers.Server.ToString() : string.Empty;\n\n//Retrieving a custom header value\nvar customHeaderValue = string.Join(',', response.Headers.GetValues(\"A-Custom-Header\"));\n\n//Looping through all the headers\nforeach(var header in response.Headers)\n{\n    var headerName = header.Key;\n    var headerValue = string.Join(',', header.Value);\n}\n\n//Finally, retrieving the content in the response body as a strongly-typed object\nvar user = response.Content;\n```\n\n### Using generic interfaces\n\nWhen using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. Refit now supports these, allowing you to define a single API interface with a generic type:\n\n```csharp\npublic interface IReallyExcitingCrudApi\u003cT, in TKey\u003e where T : class\n{\n    [Post(\"\")]\n    Task\u003cT\u003e Create([Body] T payload);\n\n    [Get(\"\")]\n    Task\u003cList\u003cT\u003e\u003e ReadAll();\n\n    [Get(\"/{key}\")]\n    Task\u003cT\u003e ReadOne(TKey key);\n\n    [Put(\"/{key}\")]\n    Task Update(TKey key, [Body]T payload);\n\n    [Delete(\"/{key}\")]\n    Task Delete(TKey key);\n}\n```\n\nWhich can be used like this:\n\n```csharp\n// The \"/users\" part here is kind of important if you want it to work for more\n// than one type (unless you have a different domain for each type)\nvar api = RestService.For\u003cIReallyExcitingCrudApi\u003cUser, string\u003e\u003e(\"http://api.example.com/users\");\n```\n### Interface inheritance\n\nWhen multiple services that need to be kept separate share a number of APIs, it is possible to leverage interface inheritance to avoid having to define the same Refit methods multiple times in different services:\n\n```csharp\npublic interface IBaseService\n{\n    [Get(\"/resources\")]\n    Task\u003cResource\u003e GetResource(string id);\n}\n\npublic interface IDerivedServiceA : IBaseService\n{\n    [Delete(\"/resources\")]\n    Task DeleteResource(string id);\n}\n\npublic interface IDerivedServiceB : IBaseService\n{\n    [Post(\"/resources\")]\n    Task\u003cstring\u003e AddResource([Body] Resource resource);\n}\n```\n\nIn this example, the `IDerivedServiceA` interface will expose both the `GetResource` and `DeleteResource` APIs, while `IDerivedServiceB` will expose `GetResource` and `AddResource`.\n\n#### Headers inheritance\n\nWhen using inheritance, existing header attributes will be passed along as well, and the inner-most ones will have precedence:\n\n```csharp\n[Headers(\"User-Agent: AAA\")]\npublic interface IAmInterfaceA\n{\n    [Get(\"/get?result=Ping\")]\n    Task\u003cstring\u003e Ping();\n}\n\n[Headers(\"User-Agent: BBB\")]\npublic interface IAmInterfaceB : IAmInterfaceA\n{\n    [Get(\"/get?result=Pang\")]\n    [Headers(\"User-Agent: PANG\")]\n    Task\u003cstring\u003e Pang();\n\n    [Get(\"/get?result=Foo\")]\n    Task\u003cstring\u003e Foo();\n}\n```\n\nHere, `IAmInterfaceB.Pang()` will use `PANG` as its user agent, while `IAmInterfaceB.Foo` and `IAmInterfaceB.Ping` will use `BBB`.\nNote that if `IAmInterfaceB` didn't have a header attribute, `Foo` would then use the `AAA` value inherited from `IAmInterfaceA`.\nIf an interface is inheriting more than one interface, the order of precedence is the same as the one in which the inherited interfaces are declared:\n\n```csharp\npublic interface IAmInterfaceC : IAmInterfaceA, IAmInterfaceB\n{\n    [Get(\"/get?result=Foo\")]\n    Task\u003cstring\u003e Foo();\n}\n```\n\nHere `IAmInterfaceC.Foo` would use the header attribute inherited from `IAmInterfaceA`, if present, or the one inherited from `IAmInterfaceB`, and so on for all the declared interfaces.\n\n### Default Interface Methods\nStarting with C# 8.0, default interface methods (a.k.a. DIMs) can be defined on interfaces. Refit interfaces can provide additional logic using DIMs, optionally combined with private and/or static helper methods:\n```csharp\npublic interface IApiClient\n{\n    // implemented by Refit but not exposed publicly\n    [Get(\"/get\")]\n    internal Task\u003cstring\u003e GetInternal();\n    // Publicly available with added logic applied to the result from the API call\n    public async Task\u003cstring\u003e Get()\n        =\u003e FormatResponse(await GetInternal());\n    private static String FormatResponse(string response)\n        =\u003e $\"The response is: {response}\";\n}\n```\nThe type generated by Refit will implement the method `IApiClient.GetInternal`. If additional logic is required immediately before or after its invocation, it shouldn't be exposed directly and can thus be hidden from consumers by being marked as `internal`.\nThe default interface method `IApiClient.Get` will be inherited by all types implementing `IApiClient`, including - of course - the type generated by Refit.\nConsumers of the `IApiClient` will call the public `Get` method and profit from the additional logic provided in its implementation (optionally, in this case, with the help of the private static helper `FormatResponse`).\nTo support runtimes without DIM-support (.NET Core 2.x and below or .NET Standard 2.0 and below), two additional types would be required for the same solution.\n```csharp\ninternal interface IApiClientInternal\n{\n    [Get(\"/get\")]\n    Task\u003cstring\u003e Get();\n}\npublic interface IApiClient\n{\n    public Task\u003cstring\u003e Get();\n}\ninternal class ApiClient : IApiClient\n{\n    private readonly IApiClientInternal client;\n    public ApiClient(IApiClientInternal client) =\u003e this.client = client;\n    public async Task\u003cstring\u003e Get()\n        =\u003e FormatResponse(await client.Get());\n    private static String FormatResponse(string response)\n        =\u003e $\"The response is: {response}\";\n}\n```\n\n### Using HttpClientFactory\n\nRefit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to `Refit.HttpClientFactory` and call\nthe provided extension method in your `ConfigureServices` method to configure your Refit interface:\n\n```csharp\nservices.AddRefitClient\u003cIWebApi\u003e()\n        .ConfigureHttpClient(c =\u003e c.BaseAddress = new Uri(\"https://api.example.com\"));\n        // Add additional IHttpClientBuilder chained methods as required here:\n        // .AddHttpMessageHandler\u003cMyHandler\u003e()\n        // .SetHandlerLifetime(TimeSpan.FromMinutes(2));\n```\n\nOptionally, a `RefitSettings` object can be included:\n```csharp\nvar settings = new RefitSettings();\n// Configure refit settings here\n\nservices.AddRefitClient\u003cIWebApi\u003e(settings)\n        .ConfigureHttpClient(c =\u003e c.BaseAddress = new Uri(\"https://api.example.com\"));\n        // Add additional IHttpClientBuilder chained methods as required here:\n        // .AddHttpMessageHandler\u003cMyHandler\u003e()\n        // .SetHandlerLifetime(TimeSpan.FromMinutes(2));\n\n// or injected from the container\nservices.AddRefitClient\u003cIWebApi\u003e(provider =\u003e new RefitSettings() { /* configure settings */ })\n        .ConfigureHttpClient(c =\u003e c.BaseAddress = new Uri(\"https://api.example.com\"));\n        // Add additional IHttpClientBuilder chained methods as required here:\n        // .AddHttpMessageHandler\u003cMyHandler\u003e()\n        // .SetHandlerLifetime(TimeSpan.FromMinutes(2));\n\n```\nNote that some of the properties of `RefitSettings` will be ignored because the `HttpClient` and `HttpClientHandlers` will be managed by the `HttpClientFactory` instead of Refit.\n\nYou can then get the api interface using constructor injection:\n\n```csharp\npublic class HomeController : Controller\n{\n    public HomeController(IWebApi webApi)\n    {\n        _webApi = webApi;\n    }\n\n    private readonly IWebApi _webApi;\n\n    public async Task\u003cIActionResult\u003e Index(CancellationToken cancellationToken)\n    {\n        var thing = await _webApi.GetSomethingWeNeed(cancellationToken);\n        return View(thing);\n    }\n}\n```\n\n### Providing a custom HttpClient\n\nYou can supply a custom `HttpClient` instance by simply passing it as a parameter to the `RestService.For\u003cT\u003e` method:\n\n```csharp\nRestService.For\u003cISomeApi\u003e(new HttpClient()\n{\n    BaseAddress = new Uri(\"https://www.someapi.com/api/\")\n});\n```\n\nHowever, when supplying a custom `HttpClient` instance the following `RefitSettings` properties will not work:\n\n* `AuthorizationHeaderValueGetter`\n* `HttpMessageHandlerFactory`\n\nIf you still want to be able to configure the `HtttpClient` instance that `Refit` provides while still making use of the above settings, simply expose the `HttpClient` on the API interface:\n\n```csharp\ninterface ISomeApi\n{\n    // This will automagically be populated by Refit if the property exists\n    HttpClient Client { get; }\n\n    [Headers(\"Authorization: Bearer\")]\n    [Get(\"/endpoint\")]\n    Task\u003cstring\u003e SomeApiEndpoint();\n}\n```\n\nThen, after creating the REST service, you can set any `HttpClient` property you want, e.g. `Timeout`:\n\n```csharp\nSomeApi = RestService.For\u003cISomeApi\u003e(\"https://www.someapi.com/api/\", new RefitSettings()\n{\n    AuthorizationHeaderValueGetter = (rq, ct) =\u003e GetTokenAsync()\n});\n\nSomeApi.Client.Timeout = timeout;\n```\n\n### Handling exceptions\nRefit has different exception handling behavior depending on if your Refit interface methods return `Task\u003cT\u003e` or if they return `Task\u003cIApiResponse\u003e`, `Task\u003cIApiResponse\u003cT\u003e\u003e`, or `Task\u003cApiResponse\u003cT\u003e\u003e`.\n\n#### \u003ca id=\"when-returning-taskapiresponset\"\u003e\u003c/a\u003eWhen returning `Task\u003cIApiResponse\u003e`, `Task\u003cIApiResponse\u003cT\u003e\u003e`, or `Task\u003cApiResponse\u003cT\u003e\u003e`\nRefit traps any `ApiException` raised by the `ExceptionFactory` when processing the response, and any errors that occur when attempting to deserialize the response to `ApiResponse\u003cT\u003e`, and populates the exception into the `Error` property on `ApiResponse\u003cT\u003e` without throwing the exception.\n\nYou can then decide what to do like so:\n\n```csharp\nvar response = await _myRefitClient.GetSomeStuff();\nif(response.IsSuccessful)\n{\n   //do your thing\n}\nelse\n{\n   _logger.LogError(response.Error, response.Error.Content);\n}\n```\n\n\u003e [!NOTE]\n\u003e The `IsSuccessful` property checks whether the response status code is in the range 200-299 and there wasn't any other error (for example, during content deserialization). If you just want to check the HTTP response status code, you can use the `IsSuccessStatusCode` property.\n\n#### When returning `Task\u003cT\u003e`\nRefit throws any `ApiException` raised by the `ExceptionFactory` when processing the response and any errors that occur when attempting to deserialize the response to `Task\u003cT\u003e`.\n\n```csharp\n// ...\ntry\n{\n   var result = await awesomeApi.GetFooAsync(\"bar\");\n}\ncatch (ApiException exception)\n{\n   //exception handling\n}\n// ...\n```\n\nRefit can also throw `ValidationApiException` instead which in addition to the information present on `ApiException` also contains `ProblemDetails` when the service implements the [RFC 7807](https://tools.ietf.org/html/rfc7807) specification for problem details and the response content type is `application/problem+json`\n\nFor specific information on the problem details of the validation exception, simply catch `ValidationApiException`:\n\n```csharp\n// ...\ntry\n{\n   var result = await awesomeApi.GetFooAsync(\"bar\");\n}\ncatch (ValidationApiException validationException)\n{\n   // handle validation here by using validationException.Content,\n   // which is type of ProblemDetails according to RFC 7807\n\n   // If the response contains additional properties on the problem details,\n   // they will be added to the validationException.Content.Extensions collection.\n}\ncatch (ApiException exception)\n{\n   // other exception handling\n}\n// ...\n```\n\n#### Providing a custom `ExceptionFactory`\n\nYou can also override default exceptions behavior that are raised by the `ExceptionFactory` when processing the result by providing a custom exception factory in `RefitSettings`. For example, you can suppress all exceptions with the following:\n\n```csharp\nvar nullTask = Task.FromResult\u003cException\u003e(null);\n\nvar gitHubApi = RestService.For\u003cIGitHubApi\u003e(\"https://api.github.com\",\n    new RefitSettings {\n        ExceptionFactory = httpResponse =\u003e nullTask;\n    });\n```\n\nFor exceptions raised when attempting to deserialize the response use DeserializationExceptionFactory described bellow.\n\n#### Providing a custom `DeserializationExceptionFactory`\n\nYou can override default deserialization exceptions behavior that are raised by the `DeserializationExceptionFactory` when processing the result by providing a custom exception factory in `RefitSettings`. For example, you can suppress all deserialization exceptions with the following:\n\n```csharp\nvar nullTask = Task.FromResult\u003cException\u003e(null);\n\nvar gitHubApi = RestService.For\u003cIGitHubApi\u003e(\"https://api.github.com\",\n    new RefitSettings {\n        DeserializationExceptionFactory = (httpResponse, exception) =\u003e nullTask;\n    });\n```\n\n#### `ApiException` deconstruction with Serilog\n\nFor users of [Serilog](https://serilog.net), you can enrich the logging of `ApiException` using the\n[Serilog.Exceptions.Refit](https://www.nuget.org/packages/Serilog.Exceptions.Refit) NuGet package. Details of how to\nintegrate this package into your applications can be found [here](https://github.com/RehanSaeed/Serilog.Exceptions#serilogexceptionsrefit).\n","funding_links":["https://github.com/sponsors/reactivemarbles"],"categories":["Frameworks, Libraries and Tools","C\\#","XML Tools and Frameworks","C# #","Content","Clients","Http Client Communication","C#","Libraries","Reactive","HTTP","Tools","xml","🗒️ Cheatsheets","后端开发框架及项目"],"sub_categories":["API","In-memory data grids","30. [Refit](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Refit) , in the [API](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#api) category",".NET Clients","Http Client / REST","Mesh networks","GUI - other","📦 Libraries","后端项目_其他"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactiveui%2Frefit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freactiveui%2Frefit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactiveui%2Frefit/lists"}