{"id":13429526,"url":"https://github.com/canton7/RestEase","last_synced_at":"2025-03-16T03:31:48.617Z","repository":{"id":31595813,"uuid":"35160657","full_name":"canton7/RestEase","owner":"canton7","description":"Easy-to-use typesafe REST API client library for .NET Standard 1.1 and .NET Framework 4.5 and higher, which is simple and customisable. Inspired by Refit","archived":false,"fork":false,"pushed_at":"2023-12-10T14:55:20.000Z","size":3562,"stargazers_count":1095,"open_issues_count":13,"forks_count":108,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-03-15T01:56:54.485Z","etag":null,"topics":[],"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/canton7.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2015-05-06T13:38:13.000Z","updated_at":"2025-03-14T19:57:39.000Z","dependencies_parsed_at":"2024-11-05T15:16:16.522Z","dependency_job_id":null,"html_url":"https://github.com/canton7/RestEase","commit_stats":{"total_commits":548,"total_committers":15,"mean_commits":36.53333333333333,"dds":0.07481751824817517,"last_synced_commit":"00dfdfba00e12811b31f8063f742af4acaf41fda"},"previous_names":[],"tags_count":53,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canton7%2FRestEase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canton7%2FRestEase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canton7%2FRestEase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canton7%2FRestEase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/canton7","download_url":"https://codeload.github.com/canton7/RestEase/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243822309,"owners_count":20353496,"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":[],"created_at":"2024-07-31T02:00:41.282Z","updated_at":"2025-03-16T03:31:48.587Z","avatar_url":"https://github.com/canton7.png","language":"C#","funding_links":[],"categories":["Frameworks, Libraries and Tools","Clients","Projects using custom Source Generators \"internally\"","Libraries","Network","C# #","HTTP","框架, 库和工具","API"],"sub_categories":["API",".NET Clients","Other","Http Client / REST"],"readme":"![Project Icon](https://raw.githubusercontent.com/canton7/RestEase/master/icon.png) RestEase\n============================================================================================\n\n[![NuGet](https://img.shields.io/nuget/v/RestEase.svg)](https://www.nuget.org/packages/RestEase/) [![Build status](https://ci.appveyor.com/api/projects/status/5ap27qo5d7tm2o5n?svg=true)](https://ci.appveyor.com/project/canton7/restease)\n\nRestEase is a little type-safe REST API client library for .NET Framework 4.5.2 and higher and .NET Platform Standard 1.1 and higher, which aims to make interacting with remote REST endpoints easy, without adding unnecessary complexity.\n\nTo use it, you define an interface which represents the endpoint you wish to communicate with (more on that in a bit), where methods on that interface correspond to requests that can be made on it. RestEase will then generate an implementation of that interface for you, and by calling the methods you defined, the appropriate requests will be made. See [Installation](#installation) and [Quick Start](#quick-start) to get up and running!\n\nAlmost every aspect of RestEase can be overridden and customized, leading to a large level of flexibility.\n\nIt also works on platforms which don't support runtime code generation, such as .NET Native and iOS, if you reference [RestEase.SourceGenerator](https://www.nuget.org/packages/RestEase.SourceGenerator). See [Using RestEase.SourceGenerator](#using-resteasesourcegenerator) for more information.\n\nRestEase is built on top of [HttpClient](https://docs.microsoft.com/en-gb/dotnet/api/system.net.http.httpclient) and is deliberately a \"leaky abstraction\": it is easy to gain access to the full capabilities of HttpClient, giving you control and flexibility, when you need it.\n\nRestEase is inspired by [Anaïs Betts' Refit](https://github.com/reactiveui/refit), which in turn is inspired by Retrofit.\n\n### Table of Contents\n\n1. [Installation](#installation)\n2. [Quick Start](#quick-start)\n3. [Request Types](#request-types)\n4. [Return Types](#return-types)\n5. [Query Parameters](#query-parameters)\n    1. [Constant Query Parameters](#constant-query-parameters)\n    2. [Variable Query Parameters](#variable-query-parameters)\n        1. [Formatting Variable Query Parameters](#formatting-variable-query-parameters)\n        2. [Serialization of Variable Query Parameters](#serialization-of-variable-query-parameters)\n    3. [Query Parameters Map](#query-parameters-map)\n    4. [Raw Query String Parameters](#raw-query-string-parameters)\n    5. [Query Properties](#query-properties)\n6. [Paths](#paths)\n    1. [Base Address](#base-address)\n    2. [Base Path](#base-path)\n    3. [Path Placeholders](#path-placeholders)\n        1. [Path Parameters](#path-parameters)\n            1. [Formatting Path Parameters](#formatting-path-parameters)\n            2. [URL Encoding in Path Parameters](#url-encoding-in-path-parameters)\n            3. [Serialization of Path Parameters](#serialization-of-path-parameters)\n        2. [Path Properties](#path-properties)\n            1. [Formatting Path Properties](#formatting-path-properties)\n            2. [URL Encoding in Path Properties](#url-encoding-in-path-properties)\n            3. [Serialization of Path Properties](#serialization-of-path-properties)\n7. [Body Content](#body-content)\n    1. [URL Encoded Bodies](#url-encoded-bodies)\n8. [Response Status Codes](#response-status-codes)\n9. [Cancelling Requests](#cancelling-requests)\n10. [Headers](#headers)\n    1. [Constant Interface Headers](#constant-interface-headers)\n    2. [Variable Interface Headers](#variable-interface-headers)\n        1. [Formatting Variable Interface Headers](#formatting-variable-interface-headers)\n    3. [Constant Method Headers](#constant-method-headers)\n    4. [Variable Method Headers](#variable-method-headers)\n        1. [Formatting Variable Method Headers](#formatting-variable-method-headers) \n    5. [Redefining Headers](#redefining-headers)\n11. [Using RestEase.SourceGenerator](#using-resteasesourcegenerator)\n12. [Using HttpClientFactory](#using-httpclientfactory)\n13. [Using RestEase with Polly](#using-restease-with-polly)\n    1. [Using Polly with `RestClient`](#using-polly-with-restclient)\n    2. [Using Polly with HttpClientFactory](#using-polly-with-httpclientfactory)\n14. [HttpClient and RestEase interface lifetimes](#httpclient-and-restease-interface-lifetimes)\n15. [Controlling Serialization and Deserialization](#controlling-serialization-and-deserialization)\n    1. [Custom `JsonSerializerSettings`](#custom-jsonserializersettings)\n    2. [Custom Serializers and Deserializers](#custom-serializers-and-deserializers)\n        1. [Deserializing responses: `ResponseDeserializer`](#deserializing-responses-responsedeserializer)\n        2. [Serializing request bodies: `RequestBodySerializer`](#serializing-request-bodies-requestbodyserializer)\n        3. [Serializing request query parameters: `RequestQueryParamSerializer`](#serializing-request-query-parameters-requestqueryparamserializer)\n        4. [Serializing request path parameters: `RequestPathParamSerializer`](#serializing-request-path-parameters-requestpathparamserializer)\n        5. [Controlling query string generation: `QueryStringBuilder`](#controlling-query-string-generating-querystringbuilder)\n16. [Controlling the Requests](#controlling-the-requests)\n    1. [`RequestModifier`](#requestmodifier)\n    2. [Custom `HttpClient`](#custom-httpclient)\n    3. [Adding to `HttpRequestMessage.Properties`](#adding-to-httprequestmessageproperties)\n17. [Customizing RestEase](#customizing-restease)\n18. [Interface Accessibility](#interface-accessibility)\n19. [Using Generic Interfaces](#using-generic-interfaces)\n20. [Using Generic Methods](#using-generic-methods)\n21. [Interface Inheritance](#interface-inheritance)\n    1. [Sharing common properties and methods](#sharing-common-properties-and-methods)\n    2. [IDisposable](#idisposable)\n22. [Advanced Functionality Using Extension Methods](#advanced-functionality-using-extension-methods)\n    1. [Wrapping Other Methods](#wrapping-other-methods)\n    2. [Using `IRequester` Directly](#using-irequester-directly)\n23. [FAQs](#faqs)\n\n\nInstallation\n------------\n\n[RestEase is available on NuGet](https://www.nuget.org/packages/RestEase). See that page for installation instructions.\n\nIf you're using C# 9 or .NET 5 (or higher), reference [RestEase.SourceGenerator](https://www.nuget.org/packages/RestEase.SourceGenerator) as well to get compile-time errors and faster execution. See [Using RestEase.SourceGenerator](#using-resteasesourcegenerator) for more information. If you're targetting iOS or .NET Native, you will need to do this, as runtime code generation isn't available.\n\nIf you're using ASP.NET Core, take a look at [Using HttpClientFactory](#using-httpclientfactory). For failure handling and retries using Polly, see [Using RestEase with Polly](#using-restease-with-polly).\n\nQuick Start\n-----------\n\nTo start, first create an public interface which represents the endpoint you wish to make requests to. Please note that it does have to be public, or you must add RestEase as a friend assembly, see [Interface Accessibility below](#interface-accessibility).\n\n```csharp\nusing System;\nusing System.Threading.Tasks;\nusing Newtonsoft.Json;\nusing RestEase;\n\nnamespace RestEaseSampleApplication\n{\n    // We receive a JSON response, so define a class to deserialize the json into\n    public class User\n    {\n        public string Name { get; set; }\n        public string Blog { get; set; }\n\n        // This is deserialized using Json.NET, so use attributes as necessary\n        [JsonProperty(\"created_at\")]\n        public DateTime CreatedAt { get; set; }\n    }\n\n    // Define an interface representing the API\n    // GitHub requires a User-Agent header, so specify one\n    [Header(\"User-Agent\", \"RestEase\")]\n    public interface IGitHubApi\n    {\n        // The [Get] attribute marks this method as a GET request\n        // The \"users\" is a relative path the a base URL, which we'll provide later\n        // \"{userId}\" is a placeholder in the URL: the value from the \"userId\" method parameter is used\n        [Get(\"users/{userId}\")]\n        Task\u003cUser\u003e GetUserAsync([Path] string userId);\n    }\n\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            // Create an implementation of that interface\n            // We'll pass in the base URL for the API\n            IGitHubApi api = RestClient.For\u003cIGitHubApi\u003e(\"https://api.github.com\");\n\n            // Now we can simply call methods on it\n            // Normally you'd await the request, but this is a console app\n            User user = api.GetUserAsync(\"canton7\").Result;\n            Console.WriteLine($\"Name: {user.Name}. Blog: {user.Blog}. CreatedAt: {user.CreatedAt}\");\n            Console.ReadLine();\n        }\n    }\n}\n```\n\nRequest Types\n-------------\n\nSee the `[Get(\"path\")]` attribute used above? That's how you mark that method as being a GET request. There are a number of other attributes you can use here - in fact, there's one for each type of request: `[Get(\"path\")]`, `[Post(\"path\")]`, `[Put(\"path\")]`, `[Delete(\"path\")]`, `[Head(\"path\")]`, `[Options(\"path\")]`, `[Trace(\"path\"))]`, `[Patch(\"path\")]`. Use whichever one you need to.\n\nThe argument to `[Get]` (or `[Post]`, or whatever) is typically a relative path, and will be relative to the base uri that you provide to `RestClient.For\u003cT\u003e`. (You *can* specify an absolute path here if you need to, in which case the base uri will be ignored). Also see the section on [Paths](#paths).\n\n\nReturn Types\n------------\n\nYour interface methods may return one of the following types:\n\n - `Task`: This method does not return any data, but the task will complete when the request has completed\n - `Task\u003cT\u003e` (where `T` is not one of the types listed below): This method will deserialize the response into an object of type `T`, using Json.NET (or a custom deserializer, see [Controlling Serialization and Deserialization below](#controlling-serialization-and-deserialization)).\n - `Task\u003cstring\u003e`: This method returns the raw response, as a string (although this can be customised, [see here]((#deserializing-responses-responsedeserializer))).\n - `Task\u003cHttpResponseMessage\u003e`: This method returns the raw [`HttpResponseMessage`](https://docs.microsoft.com/en-gb/dotnet/api/system.net.http.httpresponsemessage) resulting from the request. It does not do any deserialiation. You must dispose this object after use.\n - `Task\u003cResponse\u003cT\u003e\u003e`: This method returns a `Response\u003cT\u003e`. A `Response\u003cT\u003e` contains both the deserialied response (of type `T`), but also the `HttpResponseMessage`. Use this when you want to have both the deserialized response, and access to things like the response headers. You must dispose this object after use.\n - `Task\u003cStream\u003e`: This method returns a Stream containing the response. Use this to e.g. download a file and stream it to disk. You must dispose this object after use.\n\nNon-async methods are not supported (use `.Wait()` or `.Result` as appropriate if you do want to make your request synchronous).\n\nIf you return a `Task\u003cHttpResponseMessage\u003e` or a `Task\u003cStream\u003e`, then `HttpCompletionOption.ResponseHeadersRead` is used, so that you can choose whether or not the response body should be fetched (or report its download progress, etc). If however you return a `Task\u003cT\u003e`, `Task\u003cstring\u003e`, or `Task\u003cResponse\u003cT\u003e\u003e`, then `HttpCompletionOption.ResponseContentRead` is used, meaning that any `CancellationToken` that you pass will cancel the body download.\nIf you return a `Task`, then the response body isn't fetched, unless an `ApiException` is thrown.\n\n\nQuery Parameters\n----------------\n\nIt is very common to want to include query parameters in your request (e.g. `/foo?key=value`), and RestEase makes this easy.\n\n### Constant Query Parameters\n\nThe most basic type of query parameter is a constant - the value never changes. For these, simply put the query parameter as part of the URL:\n\n```csharp\npublic interface IGitHubApi\n{\n   [Get(\"users/list?sort=desc\")]\n   Task\u003cList\u003cUser\u003e\u003e GetUsersAsync();\n}\n```\n\n### Variable Query Parameters\n\nAny parameters to a method which are:\n\n - Decorated with the `[Query]` attribute, or\n - Not decorated at all\n\nwill be interpreted as query parameters.\n\nThe name of the parameter will be used as the key, unless an argument is passed to `[Query(\"key\")]`, in which case that will be used instead.\n\nFor example:\n\n```csharp\npublic interface IGitHubApi\n{\n    [Get(\"user\")]\n    Task\u003cUser\u003e FetchUserAsync(int userid);\n\n    // Is the same as:\n\n    [Get(\"user\")]\n    Task\u003cUser\u003e FetchUserAsync([Query] int userid);\n\n    // Is the same as:\n    // (Note the casing of the parameter name)\n\n    [Get(\"user\")]\n    Task\u003cUser\u003e FetchUserAsync([Query(\"userid\")] int userId);\n}\n\nIGithubApi api = RestClient.For\u003cIGithubApi\u003e(\"http://api.github.com\");\n\n// Requests http://api.github.com/user?userid=3\nawait api.FetchUserAsync(3);\n```\n\nYou can have duplicate keys if you want:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"search\")]\n    Task\u003cSearchResult\u003e SearchAsync([Query(\"filter\")] string filter1, [Query(\"filter\")] string filter2);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests http://somenedpoint.com/search?filter=foo\u0026filter=bar\nawait api.SearchAsync(\"foo\", \"bar\");\n```\n\nYou can also have an array of query parameters:\n\n```csharp\npublic interface ISomeApi\n{\n    // You can use IEnumerable\u003cT\u003e, or any type which implements IEnumerable\u003cT\u003e\n\n    [Get(\"search\")]\n    Task\u003cSearchResult\u003e SearchAsync([Query(\"filter\")] IEnumerable\u003cstring\u003e filters);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests http://api.exapmle.com/search?filter=foo\u0026filter=bar\u0026filter=baz\nawait api.SearchAsync(new[] { \"foo\", \"bar\", \"baz\" });\n```\n\nIf you specify a key that is `null`, i.e. `[Query(null)]`, then the name of the key is not used, and the value is inserted into the query string. If you specify a key that is an empty string `\"\"`, then then query key will be left empty.\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"foo\")]\n    Task FooAsync([Query(null)] string nullParam, [Query(\"\")] string emptyParam);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/foo?onitsown\u0026=nokey\nawait api.FooAsync(\"onitsown\", \"nokey\");\n```\n\nIf you pass a value which is null, then the key is not inserted. If you pass any other value (e.g. emptystring) then the value is left empty.\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"path\")]\n    Task FooAsync([Query] string foo, [Query] string bar);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/path?bar=\nawait api.FooAsync(null, \"\");\n```\n\n#### Formatting Variable Query Parameters\n\nBy default, query parameter values will be serialized by calling `ToString()` on them. This means that the primitive types most often used as query parameters - `string`, `int`, etc - are serialized correctly.\n\nHowever, you can also specify a string format to use using the `Format` property of the `[Query]` attribute, for example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"foo\")]\n    Task FooAsync([Query(Format = \"X2\")] int param);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/foo?param=FE\nawait api.FooAsync(254);\n```\n\n1. If you use a [custom serializer](#custom-serializers-and-deserializers), then the format is passed to that serializer, and you can use it as you like.\n2. Otherwise, if the format looks like it could be passed to `string.Format`, then this happens with `param` passed as the first arg, and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"{0}\"` or `\"{0:X2}\"` or `\"hello {0}\"`.\n3. Otherwise, if `param` implements `IFormattable`, then its `ToString(string, IFormatProvider)` method is called, with `param` as the format and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"X2\"`.\n4. Otherwise, the format is ignored.\n\n\n#### Serialization of Variable Query Parameters\n\nSometimes calling `ToString()` is not enough: some APIs require that you send e.g. JSON as a query parameter. In this case, you can mark the parameter for custom serialization using `QuerySerializationMethod.Serialized`, and further control it by using a [custom serializer](#custom-serializers-and-deserializers).\n\nFor example:\n```csharp\npublic class SearchParams\n{\n    public string Term { get; set; }\n    public string Mode { get; set; }\n}\n\npublic interface ISomeApi\n{\n    [Get(\"search\")]\n    Task\u003cSearchResult\u003e SearchAsync([Query(QuerySerializationMethod.Serialized)] SearchParams param);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n// Requests https://api.example.com/search?params={\"Term\": \"foo\", \"Mode\": \"basic\"}\nawait api.SearchAsync(new SearchParams() { Term = \"foo\", Mode = \"basic\" });\n```\n\nYou can also specify the default serialization method for an entire api by specifying `[SerializationMethods(Query = QuerySerializationMethod.Serialized)]` on the interface, or for all parameters in a given method by specifying it on the method, for example:\n\n```csharp\n[SerializationMethods(Query = QuerySerializationMethod.Serialized)]\npublic interface ISomeApi\n{\n    [Get(\"search\")]\n    [SerializationMethods(Query = QuerySerializationMethod.ToString)]\n    Task\u003cSearchResult\u003e SearchWithToStringAsync([Query] SearchParams param);\n\n    [Get(\"search\")]\n    Task\u003cSearchResult\u003e SearchWithSerializedAsync([Query] SearchParams param);\n}\n```\n\n\n### Query Parameters Map\n\nSometimes you have a load of query parameters, or they're generated dynamically, etc. In this case, you may want to supply a dictionary of query parameters, rather than specifying a load of method parameters.\n\nTo facilitate this, you may decorate one or more method parameters with `[QueryMap]`. The parameter type must be an `IDictionary\u003cTKey, TValue\u003e`.\n\nQuery maps are handled the same way as other query parameters: serialization, handling of enumerables, null values, etc, behave the same. You can control whether values are serialized using a custom serializer or `ToString()` using e.g. `[QueryMap(QuerySerializationMethod.Serialized)]`.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"search\")]\n    // I've used IDictionary\u003cstring, string[]\u003e here, but you can use whatever type parameters you like,\n    // or any type which implements IDictionary\u003cTKey, TValue\u003e\n    Task\u003cSearchResult\u003e SearchBlogPostsAsync([QueryMap] IDictionary\u003cstring, string[]\u003e filters);\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\nvar filters = new Dictionary\u003cstring, string[]\u003e()\n{\n    { \"title\", new[] { \"bobby\" } },\n    { \"tag\", new[] { \"c#\", \"programming\" } }\n};\n\n// Requests https://api.example.com/search?title=bobby\u0026tag=c%23\u0026tag=programming\nvar searchResults = await api.SearchBlogPostsAsync(filters);\n```\n\n### Raw Query String Parameters\n\nIn rare cases, you may have generated a query string by other means, and want to give this to RestEase. To do this, provide one or more parameters decorated with `[RawQueryString]`.\n\nThis parameter can be of any type, and `.ToString()` will be called on it to turn it into a string. Its value will be prepended, verbatim, to the query string: you are responsible for any escaping. It must not begin or end with \"\u0026\" or \"?\".\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"search\")]\n    Task\u003cSearchResult\u003e SearchAsync([RawQueryString] string customFilter);\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\nvar filter = \"filter=foo\";\n// Requests https://api.example.com?filter=foo\nvar searchResults = await api.SearchAsync(filter);\n```\n\n### Query Properties\n\nIf you want to have a query string which is included in all of your requests, you can do this by declaring a `[Query]` property. These work the same way as query parameters, but they apply to all methods in your interface. If the value of the property is `null`, then it will not be added to your query. Otherwise, it will always be present, even if you declare a query parameter with the same name.\n\nThe property must have both a getter and a setter.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n\t[Query(\"foo\")]\n\tstring Foo { get; set; }\n\n\t[Get(\"thing\")]\n\tTask ThingAsync([Query] string foo);\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\napi.Foo = \"bar\";\n\n// Requests https://api.example.com?foo=baz\u0026foo=bar\nawait api.ThingAsync(\"baz\");\n```\n\nPaths\n-----\n\nThe path to which a request is sent is constructed from the following 3 parts, concatenated together (and separated with `/`):\n\n1. The Base Address (e.g. `https://api.example.com`)\n2. The Base Path (optional, e.g. `api/v1`)\n3. The path specified on the method, passed to the `[Get(\"path\")]`, etc, attribute (e.g. `users`)\n\nThe Base Address is the domain at which the API can be found. This is normally specified by passing an address to `RestClient.For\u003cT\u003e(...)`, but you can also use `[BaseAddress(...)]`, see [Base Address](#base-address).\n\nThe Base Path is optional, and is a prefix which is common to all of your API's paths, e.g. `api/v1`. If you like, you can specify this once using the `[BasePath(...)]` attribute, see [Base Path](#base-path).\n\nEach method also has a path, which is passed to the `[Get(...)]`, etc, attribute on the method.\n\nOrdinarily, these three parts are concatenated together, giving e.g. `https://api.example.com/api/v1/users`. However, there are a number of extra rules:\n\n1. If the path specified on the method begins with a `/`, then the Base Path is ignored (but the Base Address is not ignored). So if the method's path was `/users` instead of `users`, the final address would be `https://api.example.com/users`.\n2. If the path specified on the method is absolute (e.g. it begins with `http://`), then both the Base Address and Base Path are ignored.\n3. If the Base Path is absolute, then the Base Address is ignored.\n\nThese rules are particularly useful when working with an API which returns links to other endpoints, see [URL Encoding in Path Parameters](#url-encoding-in-path-parameters) for an example.\n\n### Base Address\n\nThe Base Address can be specified in two ways:\n\n1. When instantiating the API using `RestClient`, either by passing a URI to `RestClient.For\u003cT\u003e(...)` or `new RestClient(...)`, or by passing a `HttpClient` which has its `BaseAddress` property set.\n2. Using a `[BaseAddress(...)]` attribute on the interface itself.\n\nIf it's specified both ways, then the `[BaseAddress(...)]` attribute is ignored. This means that you can have a default Base Address specified on the interface, but this can be overridden when actually instantiating it using `RestClient`.\n\nThe Base Address can contain [`{placeholders}`](#path-placeholders). Each placeholder must have a corresponding [path property](#path-properties), although this will be overridden by a [path parameter](#path-parameters) if one is present.\n\nThe Base Address may contain the start of a path as well, e.g. `https://api.example.com/api/v1`. This path will not be overridden if the path specified on the method starts with a `/`, in contrast to the Base Path.\n\nThe Base Address must be absolute (i.e. it must start with `http://` or `https://`).\n\nQuery strings or fragments are not supported in the Base Address, and their behaviour is undefined and subject to change. \n\n### Base Path\n\nThe Base Path is specified using a `[BasePath(...)]` attribute on your interface.\n\nThe Base Path can contain [`{placeholders}`](#path-placeholders). Each placeholder must have a corresponding [path property](#path-properties), although this will be overridden by a [path parameter](#path-parameters) if one is present.\n\nQuery strings, or other parts of a URI, are not supported in the Base Path, and their behaviour is undefined and subject to change.\n\n\n### Path Placeholders\n\nSometimes you also want to be able to control some parts of the path itself, rather than just the query parameters.\n\n#### Path Parameters\n\nPath parameters are the most common means of controlling a part of the path. This is done using placeholders in the path, and corresponding method parameters decorated with `[Path]`.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"user/{userId}\")]\n    Task\u003cUser\u003e FetchUserAsync([Path] string userId);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/user/fred\nawait api.FetchUserAsync(\"fred\");\n```\n\nAs with `[Query]`, the name of the placeholder to substitute is determined by the name of the parameter. If you want to override this, you can pass an argument to `[Path(\"placeholder\")]`, e.g.:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"user/{userId}\")]\n    Task\u003cUser\u003e FetchUserAsync([Path(\"userId\")] string idOfTheUser);\n}\n```\n\nEvery placeholder must have a corresponding parameter, and every parameter must relate to a placeholder.\n\n##### Formatting Path Parameters\n\nAs with `[Query]`, path parameter values will be serialized by calling `ToString()` on them. This means that the primitive types most often used as query parameters - `string`, `int`, etc - are serialized correctly.\n\nHowever, you can also specify a format using the `Format` property of the `[Path]` attribute, for example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"foo/{bar}\")]\n    Task FooAsync([Path(\"bar\", Format = \"D2\")] int param);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/foo/01\nawait api.FooAsync(1);\n```\n\n1. If you use a [custom serializer](#custom-serializers-and-deserializers), then the format is passed to that serializer, and you can use it as you like.\n2. Otherwise, if the format looks like it could be passed to `string.Format`, then this happens with `param` passed as the first arg, and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"{0}\"` or `\"{0:D2}\"` or `\"hello {0}\"`.\n3. Otherwise, if `param` implements `IFormattable`, then its `ToString(string, IFormatProvider)` method is called, with `param` as the format and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"D2\"`.\n4. Otherwise, the format is ignored.\n\n##### URL Encoding in Path Parameters\n\nBy default, path parameters are URL-encoded, which means things like `/` are escaped. If you don't want this, for example you want to specify a literal section of the URL, this can be disabled using the `UrlEncode` property of the `[Path]` attribute, for example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"foo/{bar}\")]\n    Task FooAsync([Path(UrlEncode = false)] string bar);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/foo/bar/baz\nawait api.FooAsync(\"bar/baz\");\n```\n\nThis can be useful if working with an API which returns raw links to other resources, when combined with the logic specified in [Paths](#paths). For example, let's say we want to make a request to `https://api.example.com/v1/first`, and that gives us back:\n\n```json\n{\n    \"Second\": \"/v1/second\"\n}\n```\n\nWe could write:\n\n```cs\n[BasePath(\"v1\")]\npublic interface ISomeApi\n{\n    [Get(\"first\")]\n    Task\u003cFirstResponse\u003e GetFirstAsync();\n\n    [Get(\"{url}\")]\n    Task GetSecondAsync([Path] string url);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\nvar response = await api.GetFirstAsync();\nawait api.GetSecondAsync(response.Second);\n```\n\n##### Serialization of Path Parameters\n\nSimilar to query parameters, calling `ToString()` is sometimes not enough: you might want to customize how your path parameters are turned into strings (for example, for enum members). In this case, you can mark the parameter for custom serialization using `PathSerializationMethod.Serialized`, and specifying a [`RequestPathParamSerializer`](#serializing-request-path-parameters-requestpathparamserializer).\n\nFor example:\n```csharp\npublic enum MyEnum\n{\n    [Display(Name = \"first\")]\n    First,\n\n    [Display(Name = \"second\")]\n    Second,\n}\n\npublic interface ISomeApi\n{\n    [Get(\"path/{param}\")]\n    Task\u003cstring\u003e GetAsync([Path(PathSerializationMethod.Serialized)] MyEnum param);\n}\n\nISomeApi api = new RestClient()\n{\n    RequestPathParamSerializer = new StringEnumRequestPathParamSerializer()\n}.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Requests https://api.example.com/path/first\nawait api.GetAsync(MyEnum.First);\n```\n\nYou can also specify the default serialization method for an entire api by specifying `[SerializationMethods(Path = PathSerializationMethod.Serialized)]` on the interface, or for all parameters in a given method by specifying it on the method, for example:\n\n```csharp\n[SerializationMethods(Path = PathSerializationMethod.Serialized)]\npublic interface ISomeApi\n{\n    [Get(\"path/{foo}\")]\n    [SerializationMethods(Path = PathSerializationMethod.ToString)]\n    Task\u003cstring\u003e GetWithToStringAsync([Path] MyEnum foo);\n\n    [Get(\"path/{foo}\")]\n    Task\u003cstring\u003e GetWithSerializedAsync([Path] MyEnum foo);\n}\n```\n\n#### Path Properties\n\nSometimes you've got a placeholder which is present in all (or most) of the paths on the interface, for example an account ID. In this case, you can specify a `[Path]` property. These work in the same way as path parameters, but they're on the level of the entire API.\n\nProperties must have both a getter and a setter. If the placeholder of the path property isn't given (i.e. you use `[Path]` instead of `[Path(\"placeholder\")]`), then the name of the property will be used.\n\nUnlike with path parameters, you don't *need* to have the placeholder present in every path. If you have both a path parameter and a path property with the same name, the path parameter is used.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Path(\"accountId\")]\n    int AccountId { get; set; }\n\n    [Get(\"{accountId}/profile\")]\n    Task\u003cProfile\u003e GetProfileAsync();\n\n    [Delete(\"{accountId}\")]\n    Task DeleteAsync([Path(\"accountId\")] int accountId);\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com/user\");\napi.AccountId = 3;\n\n// Requests https://api.example.com/user/3/profile\nvar profile = await api.GetProfileAsync();\n\n// Requests https://api.example.com/user/4/profile\nawait api.DeleteAsync(4);\n```\n\nYou can also use [`BasePath`](#base-path) if all of your paths start with `{accountId}`.\n\n##### Formatting Path Properties\n\nAs with Path Parameters, you can specify a string format to use if the value implements `IFormattable`.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Path(\"accountId\", Format = \"N\")]\n    Guid AccountId { get; set; }\n\n    [Get(\"{accountId}/profile\")]\n    Task\u003cProfile\u003e GetProfileAsync();\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com/user\");\napi.AccountId = someGuid;\n\n// Requests e.g. /user/00000000000000000000000000000000 /profile\nvar profile = await api.GetProfileAsync();\n```\n\n##### URL Encoding in Path Properties\n\nAs with path parameters, you can disable URL encoding for path properties.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Path(\"pathPart\", UrlEncode = false)]\n    Guid PathPart { get; set; }\n\n    [Get(\"{pathPart}/profile\")]\n    Task GetAsync();\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\napi.PathPart = \"users/abc\";\n\n// Requests https://api.example.com/users/abc/profile\nawait api.GetAsync();\n```\n\n##### Serialization of Path Properties\n\n[As with path parameters](#serialization-of-path-parameters), you can specify `PathSerializationMethod.Serialized` on a path property to use custom serialization behaviour. You must also supply a `RequestPathParamSerializer` when creating the `RestClient`. This can be used for things like controlling how enum members are serialized.\n\nFor example:\n\n```csharp\npublic enum MyEnum\n{\n    [Display(Name = \"first\")]\n    First,\n\n    [Display(Name = \"second\")]\n    Second,\n}\n\npublic interface ISomeApi\n{\n    [Path(\"pathPart\", PathSerializationMethod.Serialized)]\n    MyEnum PathPart { get; set; }\n\n    [Get(\"path/{pathPart}\")]\n    Task\u003cstring\u003e GetAsync();\n}\n\nISomeApi api = new RestClient()\n{\n    RequestPathParamSerializer = new StringEnumRequestPathParamSerializer()\n}.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\napi.PathPart = MyEnumSecond;\n// Requests https://api.example.com/path/second\nawait api.GetAsync();\n```\n\nBody Content\n------------\n\nIf you're sending a request with a body, you can specify that one of the parameters to your method contains the body you want to send, using the `[Body]` attribute.\n\n```csharp\npublic interface ISomeApi\n{\n    [Post(\"users/new\")]\n    Task CreateUserAsync([Body] User user);\n}\n```\n\nExactly how this will be serialized depends on the type of parameters:\n\n - If the type is `Stream`, then the content will be streamed via [`StreamContent`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.streamcontent).\n - If the type is `String`, then the string will be used directly as the content (using [`StringContent`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.stringcontent?)).\n - If the type is `byte[]`, then the byte array will be used directory as the content (using [`ByteArrayContent`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.bytearraycontent)).\n - If the parameter has the attribute `[Body(BodySerializationMethod.UrlEncoded)]`, then the content will be URL-encoded ([see below](#url-encoded-bodies)).\n - If the type is an [`HttpContent`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpcontent) (or one of its subclasses), then it will be used directly. This is useful for advanced scenarios.\n - Otherwise, the parameter will be serialized as JSON (by default, or you can customize this if you want, see [Controlling Serialization and Deserialization](#controlling-serialization-and-deserialization)).\n\n\n### URL Encoded Bodies\n\nFor APIs which take form posts (i.e. serialized as `application/x-www-form-urlencoded`), initialize the `[Body]` attribute with `BodySerializationMethod.UrlEncoded`. This parameter must implement `IDictionary` or `IDictionary\u003cTKey, TValue\u003e`.\n\nIf any of the values implement `IEnumerable`, then they will be serialized as an array of values.\n\nFor example:\n\n```csharp\npublic interface IMeasurementProtocolApi\n{\n    [Post(\"collect\")]\n    Task CollectAsync([Body(BodySerializationMethod.UrlEncoded)] Dictionary\u003cstring, object\u003e data);\n}\n\nvar data = new Dictionary\u003cstring, object\u003e {\n    {\"v\", 1},\n    {\"tids\", new[] { \"UA-1234-5\", \"UA-1234-6\" },\n    {\"cid\", new Guid(\"d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c\")},\n    {\"t\", \"event\"},\n};\n\n// Serialized as: v=1\u0026tids=UA-1234-5\u0026tids=UA-1234-6\u0026cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c\u0026t=event\nawait api.CollectAsync(data);\n ```\n\nYou can also control the default body serialization method for an entire API by specifying `[SerializationMethods(Body = BodySerializationMethod.UrlEncoded)]` on the interface itself:\n\n```csharp\n[SerializationMethods(Body = BodySerializationMethod.UrlEncoded)]\npublic interface ISomeApi\n{\n    [Post(\"collect\")]\n    Task CollectAsync([Body] Dictionary\u003cstring, object\u003e data);\n}\n```\n\n\nResponse Status Codes\n---------------------\n\nBy default, any response status code which does not indicate success (as indicated by [`HttpResponseMessage.IsSuccessStatusCode`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage.issuccessstatuscode)) will cause an `ApiException` to be thrown.\n\nThe `ApiException` has properties which tell you exactly what happened (such as the `HttpStatusCode`, the URI which was requested, the string content, and also a method `DeserializeContent\u003cT\u003e()` to let you attempt to deserialize the content as a particular type). This means that you can write code such as:\n\n```cs\ntry\n{\n    var response = await client.SayHelloAsync();\n}\ncatch (ApiException e) when (e.StatusCode == HttpStatusCode.NotFound)\n{\n    var notFoundResponse = e.DeserializeContent\u003cNotFoundRepsonse\u003e();\n    // ...\n}\n```\n\nThis is usually what you want, but sometimes you're expecting failure.\n\nIn this case, you can apply `[AllowAnyStatusCode]` to you method, or indeed to the whole interface, to suppress this behaviour. If you do this, then you probably want to make your method return either a `HttpResponseMessage` or a `Response\u003cT\u003e` (see [Return Types](#return-types)) so you can examine the response code yourself.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"users/{userId}\")]\n    [AllowAnyStatusCode]\n    Task\u003cResponse\u003cUser\u003e\u003e FetchUserThatMayNotExistAsync([Path] int userId);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\nusing (var response = await api.FetchUserThatMayNotExistAsync(3));\nif (response.ResponseMessage.StatusCode == HttpStatusCode.NotFound)\n{\n    // User wasn't found\n}\nelse\n{\n    var user = response.GetContent();\n    // ...\n}\n```\n\nCancelling Requests\n-------------------\n\nIf you want to be able to cancel a request, pass a `CancellationToken` as one of the method paramters.\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"very-large-response\")]\n    Task\u003cLargeResponse\u003e GetVeryLargeResponseAsync(CancellationToken cancellationToken);\n}\n```\n\nNote that if your method returns a `Task\u003cHttpResponseMessage\u003e` or `Task\u003cStream\u003e`, then the `CancellationToken` will not cancel the download of the response body, see [Return Types](#return-types) for details.\n\n\nHeaders\n-------\n\nSpecifying headers is actually a surprisingly large topic, and can be done in several ways, depending on the precise behaviour you want.\n\n### Constant Interface Headers\n\nIf you want to have a header that applies to every single request, and whose value is fixed, use a constant interface header. These are specified as `[Header(\"Name\", \"Value\")]` attributes on the interface.\n\nFor example:\n\n```csharp\n[Header(\"User-Agent\", \"RestEase\")]\n[Header(\"Cache-Control\", \"no-cache\")]\npublic interface IGitHubApi\n{\n    [Get(\"users\")]\n    Task\u003cList\u003cUser\u003e\u003e GetUsersAsync();\n}\n```\n\n### Variable Interface Headers\n\nIf you want to have a header that applies to every single request, and whose value is variable, then use a variable interface header. These are specifed using properties, using a `[Header(\"Name\")]` attribute on that property.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Header(\"X-API-Key\")]\n    string ApiKey { get; set; }\n\n    [Get(\"users/{userId}\")]\n    Task\u003cUser\u003e FetchUserId([Path] string userId);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\")\napi.ApiKey = \"The-API-KEY-value\";\n// ...\n```\n\nFor nullable property types, you can also specify a default (which will be used when the property is null):\n\n```csharp\npublic interface ISomeApi\n{\n    [Header(\"X-API-Key\", \"None\")]\n    string ApiKey { get; set; }\n\n    [Get(\"users/{userId}\")]\n    Task\u003cUser\u003e FetchUserAsync([Path] string userId);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\")\n\n// \"X-API-Key: None\"\nvar user = await api.FetchUserAsync(\"bob\");\n```\n\n#### Formatting Variable Interface Headers\n\nBy default, variable interface header values will be serialized by calling `ToString()` on them. This means that the primitive types most often used as query parameters - `string`, `int`, etc - are serialized correctly.\n\nHowever, you can also specify a string format to use using the `Format` property of the `[Query]` attribute, for example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Header(\"SomeHeader\", Format = \"X2\")]\n    int SomeHeader { get; set; }\n\n    [Get(\"foo\")]\n    Task FooAsync();\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\napi.SomeHeader = 254;\n// SomeHeader: FE\nawait api.FooAsync();\n```\n\n1. If the format looks like it could be passed to `string.Format`, then this happens with `SomeHeader` passed as the first arg, and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"{0}\"` or `\"{0:X2}\"` or `\"hello {0}\"`.\n2. Otherwise, if `SomeHeader` implements `IFormattable`, then its `ToString(string, IFormatProvider)` method is called, with `SomeHeader` as the format and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"X2\"`.\n3. Otherwise, the format is ignored.\n\n### Constant Method Headers\n\nIf you want to have a header which only applies to a particular method, and whose value never changes, then use a constant method header. Like constant interface headers, these are defined in their entirety using an attribute. However, instead of applying the attribute to the interface, you apply it to the method.\n\n```csharp\npublic interface IGitHubApi\n{\n    [Header(\"User-Agent\", \"RestEase\")]\n    [Header(\"Cache-Control\", \"no-cache\")]\n    [Get(\"users\")]\n    Task\u003cList\u003cUser\u003e\u003e GetUsersAsync();\n\n    // This method doesn't have any headers applied\n    [Get(\"users/{userId}\")]\n    Task\u003cUser\u003e GetUserAsync([Path] string userId);\n}\n```\n\n### Variable Method Headers\n\nFinally, you can have headers which only apply to a single method and whose values are variable. These consist of a `[Header(\"Name\")]` attribute applied to a method parameter.\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"users\")]\n    Task\u003cList\u003cUser\u003e\u003e GetUsersAsync([Header(\"Authorization\")] string authorization);\n}\n```\n\n#### Formatting Variable Method Headers\n\nBy default, variable method header values will be serialized by calling `ToString()` on them. This means that the primitive types most often used as query parameters - `string`, `int`, etc - are serialized correctly.\n\nHowever, you can also specify a string format to use using the `Format` property of the `[Query]` attribute, for example:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"foo\")]\n    Task FooAsync([Header(\"SomeHeader\", Format = \"X2\")] int someHeader);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n// SomeHeader: FE\nawait api.FooAsync(254);\n```\n\n1. If the format looks like it could be passed to `string.Format`, then this happens with `someHeader` passed as the first arg, and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"{0}\"` or `\"{0:X2}\"` or `\"hello {0}\"`.\n2. Otherwise, if `someHeader` implements `IFormattable`, then its `ToString(string, IFormatProvider)` method is called, with `someHeader` as the format and `RestClient.FormatProvider` as the `IFormatProvider`. For example, `\"X2\"`.\n3. Otherwise, the format is ignored.\n\n### Redefining Headers\n\nYou've probably noticed that there are 4 places you can define a header: on the interface, as a property, on a method, and as a parameter (or, Constant Interface Headers, Variable Interface Headers, Constant Method Headers, and Variable Method Headers, respectively). There are rules specifying how headers from different places are merged.\n\nConstant and Variable Interface headers are merged, as are Constant and Variable Method headers. That is, if a header is supplied both as an attribute on the interface, and as a property, that header will have multiple values.\n\nMethod headers will replace Interface headers. If you have the same header on a method and on the interface, then the header on the method will replace the one on the interface.\n\nAnother rule is that a header with a value of `null` will not be added, but can still replace a previously-defined header of the same name.\n\nExample time:\n\n```csharp\n[Header(\"X-InterfaceOnly\", \"InterfaceValue\")]\n[Header(\"X-InterfaceAndParamater\", \"InterfaceValue\")]\n[Header(\"X-InterfaceAndMethod\", \"InterfaceValue\"]\n[Header(\"X-InterfaceAndParameter\", \"InterfaceValue\"]\n[Header(\"X-InterfaceAndMethod-ToBeRemoved\", \"InterfaceValue\")]\npublic interface ISomeApi\n{\n    [Header(\"X-ParameterOnly\")]\n    string ParameterOnlyHeader { get; set; }\n\n    [Header(\"X-InterfaceAndParameter\")]\n    string InterfaceAndParameterHeader { get; set; }\n\n    [Header(\"X-ParameterAndMethod\")]\n    string ParameterAndMethodHeader { get; set; }\n\n    [Get(\"url\")]\n    [Header(\"X-MethodOnly\", \"MethodValue\")]\n    [Header(\"X-MethodAndParameter\", \"MethodValue\")]\n    [Header(\"X-ParameterAndMethod\", \"MethodValue\")]\n    [Header(\"X-InterfaceAndMethod-ToBeRemoved\", null)]\n    Task DoSomethingAsync(\n        [Header(\"X-ParameterOnly\")] string parameterOnly,\n        [Header(\"X-MethodAndParameter\")] string methodAndParameter,\n        [Header(\"X-InterfaceAndParameter\")] string interfaceAndParameter\n    );\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\napi.ParameterOnlyHeader = \"ParameterValue\";\napi.InterfaceAndParameterHeader = \"ParameterValue\";\napi.ParameterAndMethodHeader = \"ParameterValue\";\n\nawait api.DoSomethingAsync(\"ParameterValue\", \"ParameterValue\", \"ParameterValue\");\n\n// Has the following headers:\n// X-InterfaceOnly: InterfaceValue\n// X-InterfaceAndParameter: InterfaceValue, ParameterValue\n// X-InterfaceAndMethod: MethodValue\n// X-InterfaceAndParameter: ParameterValue\n\n// X-ParameterAndMethod: MethodValue\n\n// X-MethodOnly: MethodValue\n// X-MethodAndParameter: MethodValue, ParameterValue\n\n// X-ParameterAndMethod-ToBeRemoved isn't set, because it was removed\n\n```\n\nUsing RestEase.SourceGenerator\n------------------------------\n\nSource Generators are a new feature which allows NuGet packages to hook into the compilation of your projects and insert their own code. RestEase uses this to generate implementations of your interfaces at compile-time, rather than run-time. To take advantage of this, you need to install the [RestEase.SourceGenerator NuGet package](https://www.nuget.org/packages/RestEase.SourceGenerator) as well as RestEase.\n\nThe advantages of using a Source Generator are:\n\n1. Compile-time error checking. Find out if your RestEase interface has an error at compile-time, rather than runtime.\n2. Supports platforms which don't support System.Reflection.Emit, such as iOS and .NET Native.\n3. Faster: no need to generate implementations at runtime.\n\nYou will need to be using the .NET 5 SDK (or higher) to make use of source generators. If you're targetting C# 9 or .NET 5 (or higher), you're all set. If you're targetting an earlier language or runtime version, you can still install the latest .NET SDK (make sure you update your global.json if you have one!): you don't need to be targetting .NET 5, you just need to be building with the .NET 5 SDK.\n\nWhen you build a project which references RestEase.SourceGenerator, RestEase generates implementations of any RestEase interfaces it finds in that project, and adds those implementations to your project. `RestClient.For\u003cT\u003e` will look for one of these implementations first, before falling back to the old approach of generating one at runtime. This means that you should reference RestEase.SourceGenerator in all projects which contain RestEase interfaces, but projects which only consume interfaces can just reference the RestEase package.\n\nAuthors of libraries which expose RestEase interfaces should also install RestEase.SourceGenerator, and the consumers of your library will use the interface implementations it generates a compile-time without needing to install RestEase.SourceGenerator themselves.\n\n\nUsing HttpClientFactory\n-----------------------\n\nIf you're using ASP.NET Core 2.1 or higher, you can set up RestEase to use HttpClientFactory. Add a reference to [RestEase.HttpClientFactory](https://nuget.org/packages/RestEase.HttpClientFactory), and add something similar to the following to your `ConfigureServices` method:\n\n```cs\nservices.AddRestEaseClient\u003cISomeApi\u003e(\"https://api.example.com\");\n```\n\nIf you want to configure the `RestClient` instance, for example to set a custom serializer, pass in an `options` with `RestClientConfigurer` set to an `Action\u003cRestClient\u003e`:\n\n```cs\nservices.AddRestEaseClient\u003cISomeApi\u003e(\"https://api.example.com\", new()\n{\n    RestClientConfigurer = client =\u003e client.RequestPathParamSerializer = new StringEnumRequestPathParamSerializer(),\n});\n```\n\nIf you want fo configure the `ISomeApi` instance, for example to assign a value to a property:\n\n```cs\nservices.AddRestEaseClient\u003cISomeApi\u003e(\"https://api.example.com\", new()\n{\n    InstanceConfigurer = instance =\u003e instance.SomeHeader = \"Some Value\",\n});\n```\n\nAn `IHttpClientBuilder` is returned, which you can call further methods on if needed:\n\n```cs\nservices.AddRestEaseClient\u003cISomeApi\u003e(\"https://api.example.com\")\n    .AddHttpMessageHandler\u003cSomeHandler\u003e()\n    .SetHandlerLifetime(TimeSpan.FromMinutes(2));\n```\n\nYou can then inject `ISomeApi` into your controllers:\n\n```cs\npublic class SomeController : ControllerBase\n{\n    private readonly ISomeApi someApi;\n    public SomeController(ISomeApi someApi) =\u003e this.someApi = someApi;\n}\n```\n\nIf you want to configure a `HttpClient` for use with multiple RestEase interfaces, you can use `IHttpClientBuilder.UseWithRestEaseClient`. This returns the `IHttpClientBuilder` it was called on, so you can call it multiple times.\n\nMake sure that you use one of the `AddHttpClient` overloads which takes a name, and also that you configure the `BaseAddress` on the `HttpClient`.\n\n```cs\nservices.AddHttpClient(\"example\")\n    .ConfigureHttpClient(x =\u003e x.BaseAddress = new Uri(\"https://api.example.com\"))\n    .UseWithRestEaseClient\u003cISomeApi\u003e()\n    .UseWithRestEaseClient\u003cISomeOtherApi\u003e();\n```\n\n\nUsing RestEase with Polly\n-------------------------\n\nSometimes request fail, and you want to retry them.\n\n[Polly](https://github.com/App-vNext/Polly) is the industry-standard framework for defining retry/failure/etc policies, and it's easy to integrate with RestEase.\n\n## Using Polly with `RestClient`\n\nIf you're working with RestEase using `new RestClient(...).For\u003cT\u003e()` or `RestClient.For\u003cT\u003e(...)`, then you'll need to install [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly/). Create your `IAsyncPolicy\u003cHttpResponseMessage\u003e` following the Polly documentation, and then tell RestEase to use it using a `PolicyHttpMessageHandler`:\n\n```cs\n// Define your policy however you want\nvar policy = Policy\n    .HandleResult\u003cHttpResponseMessage\u003e(r =\u003e r.StatusCode == HttpStatusCode.NotFound)\n    .RetryAsync();\n\n// Tell RestEase to use it\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\", new PolicyHttpMessageHandler(policy));\n// ... or ...\nvar api = new RestClient(\"https://api.example.com\", new PolicyHttpMessageHandler(policy)).For\u003cISomeApi\u003e();\n```\n\n### Using Polly with HttpClientFactory\n\nIf you're using HttpClientFactory, then you'll need to follow the instructions on [using HttpClientFactory](#using-httpclientfactory), and then install [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly/).\n\nYou can then follow [these Polly instructions](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory#configuring-the-polly-policies), but use `AddRestEaseClient` instead of `AddHttpClient`, for example:\n\n```cs\n// Define your policy however you want\nvar policy = Policy\n    .HandleResult\u003cHttpResponseMessage\u003e(r =\u003e r.StatusCode == HttpStatusCode.NotFound)\n    .RetryAsync();\n\nservices.AddRestEaseClient\u003cISomeApi\u003e(\"https://api.example.com\").AddPolicyHandler(policy);\n\n// ... or perhaps ...\n\nservices\n    .AddRestEaseClient\u003cISomeApi\u003e(\"https://api.example.com\")\n    .AddTransientHttpErrorPolicy(builder =\u003e builder.WaitAndRetryAsync(new[]\n    {\n        TimeSpan.FromSeconds(1),\n        TimeSpan.FromSeconds(5),\n        TimeSpan.FromSeconds(10)\n    }));\n```\n\n\nHttpClient and RestEase interface lifetimes\n-------------------------------------------\n\nEach instance of the interface which you define will create its own HttpClient instance.\n\nPrior to .NET Core 2.1, you should avoid creating and destroying many HttpClient instances (e.g. one per client request in a web app): instead create a single instance and keep using it ([see here](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/)).\n\nWhen using RestEase, this means that you should create a single instance of your interface and reuse it. If you use properties (e.g. Path Properties or Header Properties) which are set more than once, you could create a singleton HttpClient, and pass it to `RestClient.For\u003cT\u003e` to create many instances of your interface which share the same HttpClient.\n\nIf you're using .NET Core 2.1+, don't worry: `HttpClient` works as expected.\n\n\nControlling Serialization and Deserialization\n---------------------------------------------\n\nBy default, RestEase will use [Json.NET](http://www.newtonsoft.com/json) to deserialize responses, and serialize request bodies and query parameters. However, you can change this, either by specifying custom `JsonSerializerSettings`, or by providing your own serializers / deserializers\n\n### Custom `JsonSerializerSettings`\n\nIf you want to specify your own `JsonSerializerSettings`, you can do this by constructing a new `RestClient`, assigning `JsonSerializerSettings`, then calling `For\u003cT\u003e()` to obtain an implementation of your interface, for example:\n\n```csharp\nvar settings = new JsonSerializerSettings()\n{\n    ContractResolver = new CamelCasePropertyNamesContractResolver(),\n    Converters = { new StringEnumConverter() }\n};\n\nvar api = new RestClient(\"https://api.example.com\")\n{\n    JsonSerializerSettings = settings\n}.For\u003cISomeApi\u003e();\n```\n\n### Custom Serializers and Deserializers\n\nYou can completely customize how requests are serialized, and responses deserialized, by providing your own serializer/deserializer implementations:\n\n - To control how responses are deserialized, subclass [`ResponseDeserializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/ResponseDeserializer.cs)\n - To control how request bodies are serialized, subclass [`RequestBodySerializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/RequestBodySerializer.cs)\n - To control how request query parameters are serialized, subclass [`RequestQueryParamSerializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/RequestQueryParamSerializer.cs)\n - To control how request path parameters are serialized, subclass [`RequestPathParamSerializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/RequestPathParamSerializer.cs)\n\nYou can, of course, provide a custom implementation of only one of these, or all of them, or any number in between.\n\n#### Deserializing responses: `ResponseDeserializer`\n\nThis class has a single method, which is called whenever a response is received which needs deserializing. It is passed the `HttpResponseMessage` (so you can read headers, etc, if you want) and its `string` content which has already been asynchronously read.\n\nFor an example, see [`JsonResponseDeserializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/JsonResponseDeserializer.cs).\n\nTo tell RestEase to use it, you must create a new `RestClient`, assign its `ResponseDeserializer` property, then call `For\u003cT\u003e()` to get an implementation of your interface.\n\n```csharp\n// This API returns XML\n\npublic class XmlResponseDeserializer : ResponseDeserializer\n{\n    public override T Deserialize\u003cT\u003e(string content, HttpResponseMessage response, ResponseDeserializerInfo info)\n    {\n        // Consider caching generated XmlSerializers\n        var serializer = new XmlSerializer(typeof(T));\n\n        using (var stringReader = new StringReader(content))\n        {\n            return (T)serializer.Deserialize(stringReader);\n        }\n    }\n}\n\n// ...\n\nvar api = new RestClient(\"https://api.example.com\")\n{\n    ResponseDeserializer = new XmlResponseDeserializer()\n}.For\u003cISomeApi\u003e();\n```\n\nIf you want your deserializer to receive strings (which lets you change the default behaviour, where an interface method which returns a `Task\u003cstring\u003e` will return the raw response body), set the property `ResponseDeserializer.HandlesStrings` to `true`.\n\n#### Serializing request bodies: `RequestBodySerializer`\n\nThis class has a single method, which is called whenever a request body requires serialization (i.e. is decorated with `[Body(BodySerializationMethod.Serialized)]`). It returns any `HttpContent` subclass you like, although `StringContent` is likely to be a common choice.\n\nWhen writing an `RequestBodySerializer`'s `SerializeBody` implementation, you may choose to provide some default headers, such as `Content-Type`. These will be overidden by any `[Header]` attributes.\n\nFor an example, see [`JsonRequestBodySerializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/JsonRequestBodySerializer.cs).\n\nTo tell RestEase to use it, you must create a new `RestClient`, assign its `RequestBodySerializer` property, then call `For\u003cT\u003e()` to get an implementation of your interface.\n\nFor example:\n\n```csharp\npublic class XmlRequestBodySerializer : RequestBodySerializer\n{\n    public override HttpContent SerializeBody\u003cT\u003e(T body, RequestBodySerializerInfo info)\n    {\n        if (body == null)\n            return null;\n\n        // Consider caching generated XmlSerializers\n        var serializer = new XmlSerializer(typeof(T));\n\n        using (var stringWriter = new StringWriter())\n        {\n            serializer.Serialize(stringWriter, body);\n            var content = new StringContent(stringWriter.ToString());\n            // Set the default Content-Type header to application/xml\n            content.Headers.ContentType.MediaType = \"application/xml\";\n            return content;\n        }\n    }\n}\n\n// ...\n\nvar api = new RestClient(\"https://api.example.com\")\n{\n    RequestBodySerializer = new XmlRequestBodySerializer()\n}.For\u003cISomeApi\u003e();\n```\n\n#### Serializing request query parameters: `RequestQueryParamSerializer`\n\nThis class has two methods: one is called whenever a scalar query parameter requires serialization (i.e. is decorated with `[Query(QuerySerializationMethod.Serialized)]`); the other is called whenever a collection of query parameters (that is, the query parameter has type `IEnumerable\u003cT\u003e` for some `T`) requires serialization.\n\nBoth of these methods want you to return an `IEnumerable\u003cKeyValuePair\u003cstring, string\u003e\u003e`, where each key corresponds to the name of a query name/value pair, and each value corresponds to the value.\nFor example:\n\n```csharp\nreturn new[]\n{\n    new KeyValuePair\u003cstring, string\u003e(\"foo\", \"bar\"),\n    new KeyValuePair\u003cstring, string\u003e(\"foo\", \"baz\"),\n    new KeyValuePair\u003cstring, string\u003e(\"yay\", \"woo\")\n}\n\n// Will get serialized to '...?foo=bar\u0026foo=baz\u0026yay=woo'\n```\n\nIt is unlikely that you will return more than one `KeyValuePair` from the method which serializes scalar query parameters, but the flexibility is there.\n\nFor an example, see [`JsonRequestQueryParamSerializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/JsonRequestQueryParamSerializer.cs).\n\nTo tell RestEase to use it, you must create a new `RestClient`, assign its `RequestQueryParamSerializer` property, then call `For\u003cT\u003e()` to get an implementation of your interface.\n\nFor example:\n\n```csharp\n// It's highly unlikely that you'll get an API which requires xml-encoded query\n// parameters, but for the sake of an example:\n\npublic class XmlRequestQueryParamSerializer : RequestQueryParamSerializer\n{\n    public override IEnumerable\u003cKeyValuePair\u003cstring, string\u003e\u003e SerializeQueryParam\u003cT\u003e(string name, T value, RequestQueryParamSerializerInfo info)\n    {\n        if (value == null)\n            yield break;\n\n        // Consider caching generated XmlSerializers\n        var serializer = new XmlSerializer(typeof(T));\n\n        using (var stringWriter = new StringWriter())\n        {\n            serializer.Serialize(stringWriter, value);\n            yield return new KeyValuePair\u003cstring, string\u003e(name, stringWriter.ToString()));\n        }\n    }\n\n    public override IEnumerable\u003cKeyValuePair\u003cstring, string\u003e\u003e SerializeQueryCollectionParam\u003cT\u003e(string name, IEnumerable\u003cT\u003e values, RequestQueryParamSerializerInfo info)\n    {\n        if (values == null)\n            yield break;\n\n        // Consider caching generated XmlSerializers\n        var serializer = new XmlSerializer(typeof(T));\n\n        foreach (var value in values)\n        {\n            if (value != null)\n            {\n                using (var stringWriter = new StringWriter())\n                {\n                    serializer.Serialize(stringWriter, value);\n                    yield return new KeyValuePair\u003cstring, string\u003e(name, stringWriter.ToString()));\n                }\n            }\n        }\n    }\n}\n\nvar api = new RestClient(\"https://api.example.com\")\n{\n    RequestQueryParamSerializer = new XmlRequestQueryParamSerializer()\n}.For\u003cISomeApi\u003e();\n```\n\nIf you specified a `Format` property on the `[Query]` attribute, this will be available as `info.Format`. By default, this is `null`.\n\n#### Serializing request path parameters: `RequestPathParamSerializer`\n\nThis class has one method, called whenever a path parameter requires serialization (i.e. is decorated with `[Path(PathSerializationMethod.Serialized)]`).\n\nThis method wants you to return a `string`, which is the value that will be inserted in place of the placeholder in the path string.\n\nThere is no default path serializer, as its usage is often very specific. In order to use `PathSerializationMethod.Serialized`, you *must* set `RestClient.RequestPathParamSerializer`.\n\nThere is a [`StringEnumRequestPathParamSerializer`](https://github.com/canton7/RestEase/blob/master/src/RestEase/StringEnumRequestPathParamSerializer.cs) provided with RestEase designed for serializing enums that have [`EnumMember`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.enummemberattribute?view=netframework-4.8), [`DisplayName`](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.displaynameattribute?view=netframework-4.8) or [`Display`](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.displayattribute?view=netframework-4.8) attributes specified on their members (evaluated in that order).\nThis can be used as-is or as a reference for your own implementation.\n\nTo tell RestEase to use a path serializer, you must create a new `RestClient`, assign its `RequestPathParamSerializer` property, then call `For\u003cT\u003e()` to get an implementation of your interface.\n\nFor example:\n\n```csharp\nvar api = new RestClient(\"https://api.example.com\")\n{\n    RequestPathParamSerializer = new StringEnumRequestPathParamSerializer(),\n}.For\u003cISomeApi\u003e();\n```\n\nIf you specified a `Format` property on the `[Path]` attribute, this will be available as `info.Format`. By default, this is `null`.\n\n#### Controlling query string generation: `QueryStringBuilder`\n\nRestEase has logic to turn a collection of query parameters into a single suitably-encoded query string. However, some servers don't correctly decode query strings, and so users may want to control how query strings are encoded.\n\nTo do this, subclass [`QueryStringBuilder`](https://github.com/canton7/RestEase/blob/master/src/RestEase/QueryStringBuilder.cs) and assign it to the `RestClient.QueryStringBuilder` property. See the method `BuildQueryParam` in [`Requester`](https://github.com/canton7/RestEase/blob/master/src/RestEase/Implementation/Requester.cs) for the default implementation.\n\n\nControlling the Requests\n------------------------\n\nRestEase provides two ways for you to manipulate how exactly requests are made, before you need to resort to [Customizing RestEase](#customizing-restease).\n\n### `RequestModifier`\n\nThe first is a `RestClient.For\u003cT\u003e` overload which lets you specify a delegate which is invoked whenever a request is made. This allows you to inspect and alter the request in any way you want: changing the content, changing the headers, make your own requests in the meantime, etc.\n\nFor example, if you need to refresh an oAuth access token occasionally (using the [ADAL](https://msdn.microsoft.com/en-us/library/azure/jj573266.aspx) library as an example):\n\n```csharp\npublic interface IMyRestService\n{\n    [Get(\"getPublicInfo\")]\n    Task\u003cFoobar\u003e SomePublicMethodAsync();\n\n    [Get(\"secretStuff\")]\n    [Header(\"Authorization\", \"Bearer\")]\n    Task\u003cLocation\u003e GetLocationOfRebelBaseAsync();\n}\n\nAuthenticationContext context = new AuthenticationContext(...);\nIGitHubApi api = RestClient.For\u003cIGitHubApi\u003e(\"http://api.github.com\", async (request, cancellationToken) =\u003e\n{\n    // See if the request has an authorize header\n    var auth = request.Headers.Authorization;\n    if (auth != null)\n    {\n        // The AquireTokenAsync call will prompt with a UI if necessary\n        // Or otherwise silently use a refresh token to return a valid access token\n        var token = await context.AcquireTokenAsync(\"http://my.service.uri/app\", \"clientId\", new Uri(\"callback://complete\")).ConfigureAwait(false);\n        request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token);\n  }\n});\n\n```\n\nIf you need, you can get the `IRequestInfo` for the current request using `request.Options.TryGetValue(RestClient.HttpRequestMessageRequestInfoOptionsKey, out var requestInfo)` (for .NET 5+), or `(IRequestInfo)request.Properties[RestClient.HttpRequestMessageRequestInfoPropertyKey]` (pre .NET 5).\n\n### Custom `HttpClient`\n\nThe second is a `RestClient.For\u003cT\u003e` overload which lets you specify a custom `HttpClient` to use.\nThis lets you customize the `HttpClient`, e.g. to set the request timeout. It also lets you specify a custom `HttpMessageHandler` subclass, which allows you to control all sorts of things.\n\nFor example, if you wanted to 1) adjust the request timeout, and 2) allow invalid certificates (although the same approach would apply if you wanted to customize how certificates are validated), you could do something like this. Note that `WebRequestHandler` is a `HttpMessageHandler` subclass which allows you to specify things like `ServerCertificateValidationCallback`.\n\n```csharp\npublic class CustomHttpClientHandler : WebRequestHandler\n{\n    // Let's log all of our requests!\n    private static readonly Logger logger = LogManager.GetCurrentClassLogger();\n\n    public CustomHttpClientHandler()\n    {\n        // Allow any cert, valid or invalid\n        this.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =\u003e true;\n    }\n\n    protected override async Task\u003cHttpResponseMessage\u003e SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n    {\n        if (logger.IsTraceEnabled)\n        {\n            var response = await base.SendAsync(request, cancellationToken);\n            logger.Trace((await response.Content.ReadAsStringAsync()).Trim());\n            return response;\n        }\n        else\n        {\n            return await base.SendAsync(request, cancellationToken);\n        }\n    }\n}\n\nvar httpClient = new HttpClient(new CustomHttpClientHandler())\n{\n    BaseAddress = new Uri(\"https://secure-api.example.com\"),\n    Timeout = TimeSpan.FromSeconds(3), // Very slow to respond, this server\n};\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(httpClient);\n```\n\nIf you need, you can get the `IRequestInfo` for the current request using `(IRequestInfo)request.Properties[RestClient.HttpRequestMessageRequestInfoPropertyKey]`.\n\nAdding to `HttpRequestMessage.Properties`\n-----------------------------------------\n\nIn very specific cases (i.e. you use a custom `HttpMessageHandler`), it might be useful to pass an object reference into the handler. In such case `HttpRequestMessage.Properties` can be used.\nThis is done by decorating method parameters with `[HttpRequestMessageProperty]`. If key parameter is not specified then the name of the parameter will be used.\n\nIf all (or most) of the methods on the interface pass such object you can specify a `[HttpRequestMessageProperty]` property. These work in the same way as path parameters, but they're on the level of the entire API. Properties must have both a getter and a setter.\n\nProperty keys used at interface method level must be unique: a parameter key must not be same as a property key.\n\nFor example:\n\n```csharp\npublic interface ISomeApi\n{\n    [HttpRequestMessageProperty]\n    CommonData AlwaysIncluded { get; set; }\n\n    [Get]\n    Task Get([HttpRequestMessageProperty(\"myKey\")] string myValueIWantToAccessFromHttpMessageHandler);\n}\n```\n\nIn your `HttpMessageHandler` subclass:\n\n```csharp\nprotected override async Task\u003cHttpResponseMessage\u003e SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n{\n    var alwaysIncluded = (CommonData)request.Properties[\"AlwaysIncluded\"];\n    var myValueIWantToAccessFromHttpMessageHandler = (string)request.Properties[\"myKey\"];\n\n    // Let's use the properties!\n\n    return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);\n}\n```\n\nCustomizing RestEase\n--------------------\n\nYou've already seen how to [specify custom Serializers and Deserializers](#controlling-serialization-and-deserialization), and [control requests](#controlling-the-requests).\n\nRestEase has been written in a way which makes it very easy to customize exactly how it works. In order to describe this, I'm first going to have to outline its architecture.\n\nGiven an API like:\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"users/{userId}\")]\n    Task GetUserAsync([Path] string userId);\n}\n```\n\nCalling `RestClient.For\u003cISomeApi\u003e(...)` will cause a class like this to be generated:\n\n```csharp\nnamespace RestEase.AutoGenerated\n{\n    public class ISomeApi\n    {\n        private readonly IRequester requester;\n\n        public ISomeApi(IRequester requester)\n        {\n            this.requester = requester;\n        }\n\n        public Task GetUserAsync(string userId)\n        {\n            var requestInfo = new RequestInfo(HttpMethod.Get, \"users/{userId}\");\n            requestInfo.AddPathParameter\u003cstring\u003e(\"userId\", userId);\n            return this.requester.RequestVoidAsync(requestInfo);\n        }\n    }\n}\n```\n\nNow, you cannot customize what this generated class looks like, but you can see it doesn't actually do very much: it just builds up a `RequestInfo` object, then sends it off to the [`IRequester`](https://github.com/canton7/RestEase/blob/master/src/RestEase/IRequester.cs) (which does all of the hard work). What you *can* do however is to provide your own [`IRequester`](https://github.com/canton7/RestEase/blob/master/src/RestEase/IRequester.cs) implementation, and pass that to an appropriate overload of `RestClient.For\u003cT\u003e`. In fact, the default implementation of [`IRequester`](https://github.com/canton7/RestEase/blob/master/src/RestEase/IRequester.cs), [`Requester`](https://github.com/canton7/RestEase/blob/master/src/RestEase/Implementation/Requester.cs), has been carefully written so that it's easy to extend: each little bit of functionality is broken out into its own virtual method, so it's easy to replace just the behaviour you need.\n\nHave a read through [`Requester`](https://github.com/canton7/RestEase/blob/master/src/RestEase/Implementation/Requester.cs), figure out what you want to change, subclass it, and provide an instance of that subclass to `RestClient.For\u003cT\u003e`.\n\n\nInterface Accessibility\n-----------------------\n\nSince RestEase generates an interface implementation in a separate assembly, the interface ideally needs to be public.\n\nIf you don't want to do this, you'll need to mark RestEase as being a 'friend' assembly, which allows RestEase to see your internal types. Add the following line to your `AssemblyInfo.cs`:\n\n```\n[assembly: InternalsVisibleTo(RestEase.RestClient.FactoryAssemblyName)]\n```\n\nYou can place the interface inside any namespace, or nest the interface inside another public type if you wish.\n\n\nUsing Generic Interfaces\n------------------------\n\nWhen using something like ASP.NET Web API, it's a fairly common pattern to have a whole stack of CRUD REST services. RestEase supports these, allowing you to define a single API interface with a generic type:\n\n```csharp\npublic interface IReallyExcitingCrudApi\u003cT, TKey\u003e\n{\n    [Post(\"\")]\n    Task\u003cT\u003e Create([Body] T paylod);\n\n    [Get(\"\")]\n    Task\u003cList\u003cT\u003e\u003e ReadAll();\n\n    [Get(\"{key}\")]\n    Task\u003cT\u003e ReadOne([Path] TKey key);\n\n    [Put(\"{key}\")]\n    Task Update([Path] TKey key, [Body] T payload);\n\n    [Delete(\"{key}\")]\n    Task Delete([Path] 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 = RestClient.For\u003cIReallyExcitingCrudApi\u003cUser, string\u003e\u003e(\"https://api.example.com/users\");\n```\n\nNote that RestEase makes certain choices about how parameters and the return type are processed when the implementation of the interface is generated, and not when it is known (and the exact parameter types are known). This means that, for example, if you declare a return type of `Task\u003cT\u003e`, then call with `T` set to `String`, then you will not get a stream back - the response will be deserialized as a stream, which will almost certainly fail. Likewise if you declare a query parameter of type `T`, then set `T` to `IEnumerable\u003cstring\u003e`, then your query will contain something like `String[]`, instead of a collection of query parameters.\n\n\nUsing Generic Methods\n---------------------\n\nYou can define generic methods, if you wish. These have all of the same caveats as generic interfaces.\n\n```csharp\npublic interface IReallyExcitingCrudApi\n{\n    [Post(\"\")]\n    Task\u003cT\u003e Create\u003cT\u003e([Body] T paylod);\n\n    [Get(\"\")]\n    Task\u003cList\u003cT\u003e\u003e ReadAll\u003cT\u003e();\n\n    [Get(\"{key}\")]\n    Task\u003cT\u003e ReadOne\u003cT, TKey\u003e(TKey key);\n\n    [Put(\"{key}\")]\n    Task Update\u003cT, TKey\u003e(TKey key, [Body] T payload);\n\n    [Delete(\"{key}\")]\n    Task Delete\u003cTKey\u003e(TKey key);\n}\n```\n\n\nInterface Inheritance\n---------------------\n\n### Sharing common properties and methods\n\nYou're allowed to use interface inheritance to share common properties and methods between different APIs.\n\nYou can only put an `[AllowAnyStatusCode]` attribute on the derived interface, and not on any parent interfaces. An `[AllowAnyStatusCode]` attribute on the derived interface also applies to all methods on all parent interfaces.\n\nFor example:\n\n```csharp\npublic interface IAuthenticatedEndpoint\n{\n    [Header(\"X-Api-Token\")]\n    string ApiToken { get; set; }\n\n    [Header(\"X-Api-Username\")]\n    string ApiUsername { get; set; }\n\n    [Path(\"userId\")]\n    int UserId { get; set; }\n}\n\npublic interface IDevicesEndpoint : IAuthenticatedEndpoint\n{\n    [Get(\"/devices\")]\n    Task\u003cIList\u003cDevice\u003e\u003e GetAllDevices([QueryMap] IDictionary\u003cstring, string\u003e filters);\n}\n\npublic interface IUsersEndpoint : IAuthenticatedEndpoint\n{\n    [Get(\"/user/{userId}\")]\n    Task\u003cUser\u003e FetchUserAsync();\n}\n```\n\n### IDisposable\n\nIf your interface implements `IDisposable`, then RestEase will generate a `Dispose()` method which disposes the underlying `HttpClient`. Do this if you want to be able to dispose the `HttpClient`.\n\n\nAdvanced Functionality Using Extension Methods\n----------------------------------------------\n\nSometimes you'll have cases where you want to do something that's more complex than can be achieved using RestEase alone (e.g. uploading multipart form data), but you still want to provide a nice interface to consumers. One option is to write extension methods on your interface. There are two ways of doing this.\n\n### Wrapping other methods\n\nThe easiest thing to do is to put a method on your interface which won't be called by your code, but which can be wrapped by your extension method. This approach is unit testable.\n\n```csharp\npublic interface ISomeApi\n{\n    // Method which is only used by SomeApiExtensions\n    [Post(\"upload\")]\n    Task UploadAsync([Body] HttpContent content);\n}\n\npublic static class SomeApiExtensions\n{\n    public static Task UploadAsync(this ISomeApi api, byte[] imageData, string token)\n    {\n        var content = new MultipartFormDataContent();\n\n        var imageContent = new ByteArrayContent(imageData);\n        imageContent.Headers.ContentType = new MediaTypeHeaderValue(\"image/jpeg\");\n        content.Add(imageContent);\n\n        var tokenContent = new FormUrlEncodedContent(new[]\n        {\n            new KeyValuePair\u003cstring, string\u003e(\"token\", token)\n        });\n        content.Add(tokenContent);\n\n        return api.UploadAsync(content);\n    }\n}\n```\n\n### Using `IRequester` directly\n\nAlternatively, you can put a property of type `IRequester` on your interface, then write an extension method which uses the `IRequester`. Note that the attributes or properties you put on your interface (`ISomeApi` in the example below) will not be added to the `RequestInfo`, since you are not invoking any code which does this. Note also that this approach is not unit testable.\n\n```csharp\npublic interface ISomeApi\n{\n    IRequester Requester { get; }\n}\n\npublic static class SomeApiExtensions\n{\n    public static Task UploadAsync(this ISomeApi api, byte[] imageData, string token)\n    {\n        var content = new MultipartFormDataContent();\n\n        var imageContent = new ByteArrayContent(imageData);\n        imageContent.Headers.ContentType = new MediaTypeHeaderValue(\"image/jpeg\");\n        content.Add(imageContent);\n\n        var tokenContent = new FormUrlEncodedContent(new[]\n        {\n            new KeyValuePair\u003cstring, string\u003e(\"token\", token)\n        });\n        content.Add(tokenContent);\n\n        var requestInfo = new RequestInfo(HttpMethod.Post, \"upload\");\n        requestInfo.SetBodyParameterInfo(BodySerializationMethod.Default, content);\n        return api.Requester.RequestVoidAsync(requestInfo);\n    }\n}\n```\n\n`IRequester` and `RequestInfo` are not documented in this README. Read the doc comments.\n\nFAQs\n----\n\n### I want to use Basic Authentication\n\nSomething like this...\n\n```csharp\npublic interface ISomeApi\n{\n    [Header(\"Authorization\")]\n    AuthenticationHeaderValue Authorization { get; set; }\n\n    [Get(\"foo\")]\n    Task DoSomethingAsync();\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\nvar value = Convert.ToBase64String(Encoding.ASCII.GetBytes(\"username:password1234\"));\napi.Authorization = new AuthenticationHeaderValue(\"Basic\", value);\n\nawait api.DoSomethingAsync();\n```\n\n### I need to request an absolute path\n\nSometimes your API responses will contain absolute URLs, for example a \"next page\" link. Therefore you'll want a way to request a resource using an absolute URL which overrides the base URL you specified.\n\nThankfully this is easy: if you give an absolute URL to e.g. `[Get(\"https://api.example.com/foo\")]`, then the base URL will be ignored. You will also need to disable URL encoding.\n\n```csharp\npublic interface ISomeApi\n{\n    [Get(\"users\")]\n    Task\u003cUsersResponse\u003e FetchUsersAsync();\n\n    [Get(\"{url}\")]\n    Task\u003cUsersResponse\u003e FetchUsersByUrlAsync([Path(UrlEncode = false)] string url);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\nvar firstPage = await api.FetchUsersAsync();\n// Actually put decent logic here...\nvar secondPage = await api.FetchUsersByUrlAsync(firstPage.NextPage);\n```\n\n### I may get responses in both XML and JSON, and want to deserialize both\n\nOccasionally you get an API which can return both JSON and XML (apparently...). In this case, you'll want to auto-detect what sort of response you got, and deserialize with an appropriate deserializer.\n\nTo do this, use a custom deserializer, which can do this detection.\n\n```csharp\npublic class HybridResponseDeserializer : ResponseDeserializer\n{\n    private T DeserializeXml\u003cT\u003e(string content)\n    {\n        // Consider caching generated XmlSerializers\n        var serializer = new XmlSerializer(typeof(T));\n\n        using (var stringReader = new StringReader(content))\n        {\n            return (T)serializer.Deserialize(stringReader);\n        }\n    }\n\n    private T DeserializeJson\u003cT\u003e(string content)\n    {\n        return JsonConvert.Deserialize\u003cT\u003e(content);\n    }\n\n    public override T Deserialize\u003cT\u003e(string content, HttpResponseMessage response)\n    {\n        switch (response.Content.Headers.ContentType.MediaType)\n        {\n            case \"application/json\":\n                return this.DeserializeJson\u003cT\u003e(content);\n            case \"application/xml\":\n                return this.DeserializeXml\u003cT\u003e(content);\n        }\n\n        throw new ArgumentException(\"Response was not JSON or XML\");\n    }\n}\n\nvar api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\", new HybridResponseDeserializer());\n```\n\n### Is RestEase thread safe?\n\nYes. It is safe to create implementations of interfaces from multiple threads at the same time (and to create multiple implementations of the same interface), and it is safe to use an implementation from multiple threads at the same time.\n\n\n### I want to upload a file\n\nLet's assume you want to upload a file (from a stream), setting its name and content-type manually (skip these bits of not). There are a couple of ways of doing this, depending on your needs:\n\n```csharp\npublic interface ISomeApi\n{\n    [Header(\"Content-Disposition\", \"form-data; filename=\\\"somefile.txt\\\"\")]\n    [Header(\"Content-Type\", \"text/plain\")]\n    [Post(\"upload\")]\n    Task UploadFileVersionOneAsync([Body] Stream file);\n\n    [Post(\"upload\")]\n    // You can use strings instead of strongly-typed header values, if you want\n    Task UploadFileVersionTwoAsync(\n        [Header(\"Content-Disposition\")] ContentDispositionHeaderValue contentDisposition,\n        [Header(\"Content-Type\")] MediaTypeHeaderValue contentType,\n        [Body] Stream file);\n\n    [Post(\"upload\")]\n    Task UploadFileVersionThreeAsync([Body] HttpContent content);\n}\n\nISomeApi api = RestClient.For\u003cISomeApi\u003e(\"https://api.example.com\");\n\n// Version one (constant headers)\nusing (var fileStream = File.OpenRead(\"somefile.txt\"))\n{\n    await api.UploadFileVersionOneAsync(fileStream);\n}\n\n// Version two (variable headers)\nusing (var fileStream = File.OpenRead(\"somefile.txt\"))\n{\n    var contentDisposition = new ContentDispositionHeaderValue(\"form-header\") { FileName = \"\\\"somefile.txt\\\"\" };\n    var contentType = new MediaTypeHeaderValue(\"text/plain\");\n    await api.UploadFileVersionTwoAsync(contentDisposition, contentType, fileStream);\n}\n\n// Version three (precise control over HttpContent)\nusing (var fileStream = File.OpenRead(\"somefile.txt\"))\n{\n    var fileContent = new StreamContent(fileStream);\n    fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(\"form-header\") { FileName = \"\\\"somefile.txt\\\"\" };\n    fileContent.Headers.ContentType = new MediaTypeHeaderValue(\"text/plain\");\n    await api.UploadFileVersionThreeAsync(fileContent);\n}\n```\n\nObviously, set the headers you need - don't just copy me blindly.\n\nYou can use [extension methods](#advanced-functionality-using-extension-methods) to make this more palatable for consumers.\n\n### I want to ensure that all of the required properties on my request body are set\n\nUser @netclectic has a solution, [see this issue](https://github.com/canton7/RestEase/issues/68#issue-255988650).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanton7%2FRestEase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcanton7%2FRestEase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanton7%2FRestEase/lists"}