|
2 | 2 | title: Usage
|
3 | 3 | ---
|
4 | 4 |
|
5 |
| -## Recommended Usage |
| 5 | +## Recommended usage |
6 | 6 |
|
7 |
| -RestSharp works best as the foundation for a proxy class for your API. Here are a couple of examples from the <a href="http://github.com/twilio/twilio-csharp">Twilio</a> library. |
| 7 | +RestSharp works best as the foundation for a proxy class for your API. Each API would most probably require different settings for `RestClient`, so a dedicated API class (and its interface) gives you a nice isolation between different `RestClient` instances, and make them testable. |
8 | 8 |
|
9 |
| -Create a class to contain your API proxy implementation with an `ExecuteAsync` (or any of the extensions) method for funneling all requests to the API. |
10 |
| -This allows you to set commonly-used parameters and other settings (like authentication) shared across requests. |
11 |
| -Because an account ID and secret key are required for every request you are required to pass those two values when |
12 |
| -creating a new instance of the proxy. |
| 9 | +Essentially, RestSharp is a wrapper around `HttpClient` that allows you to do the following: |
| 10 | +- Add default parameters of any kind (not just headers) to the client, once |
| 11 | +- Add parameters of any kind to each request (query, URL segment, form, attachment, serialized body, header) in a straightforward way |
| 12 | +- Serialize the payload to JSON or XML if necessary |
| 13 | +- Set the correct content headers (content type, disposition, length, etc) |
| 14 | +- Handle the remote endpoint response |
| 15 | +- Deserialize the response from JSON or XML if necessary |
13 | 16 |
|
14 |
| -::: warning |
15 |
| -Note that exceptions from `ExecuteAsync` are not thrown but are available in the `ErrorException` property. |
16 |
| -::: |
| 17 | +As an example, let's look at a simple Twitter API v2 client, which uses OAuth2 machine-to-machine authentication. For it to work, you would need to have access to Twitter Developers portal, an a project, and an approved application inside the project with OAuth2 enabled. |
| 18 | + |
| 19 | +### Authenticator |
| 20 | + |
| 21 | +Before we can make any call to the API itself, we need to get a bearer token. Twitter exposes an endpoint `https://api.twitter.com/oauth2/token`. As it follows the OAuth2 conventions, the code can be used to create an authenticator for some other vendors. |
| 22 | + |
| 23 | +First, we need a model for deserializing the token endpoint response. OAuth2 uses snake case for property naming, so we need to decorate model properties with `JsonPropertyName` attribute: |
17 | 24 |
|
18 | 25 | ```csharp
|
19 |
| -// TwilioApi.cs |
20 |
| -public class TwilioApi { |
21 |
| - const string BaseUrl = "https://api.twilio.com/2008-08-01"; |
| 26 | +record TokenResponse { |
| 27 | + [JsonPropertyName("token_type")] |
| 28 | + public string TokenType { get; init; } |
| 29 | + [JsonPropertyName("access_token")] |
| 30 | + public string AccessToken { get; init; } |
| 31 | +} |
| 32 | +``` |
22 | 33 |
|
23 |
| - readonly RestClient _client; |
| 34 | +Next, we create the authenticator itself. It needs the API key and API key secret for calling the token endpoint using basic HTTP authentication. In addition, we can extend the list of parameters with the base URL, so it can be converted to a more generic OAuth2 authenticator. |
24 | 35 |
|
25 |
| - string _accountSid; |
| 36 | +The easiest way to create an authenticator is to inherit is from the `AuthanticatorBase` base class: |
26 | 37 |
|
27 |
| - public TwilioApi(string accountSid, string secretKey) { |
28 |
| - _client = new RestClient(BaseUrl); |
29 |
| - _client.Authenticator = new HttpBasicAuthenticator(accountSid, secretKey); |
30 |
| - _client.AddDefaultParameter( |
31 |
| - "AccountSid", _accountSid, ParameterType.UrlSegment |
32 |
| - ); // used on every request |
33 |
| - _accountSid = accountSid; |
| 38 | +```csharp |
| 39 | +public class TwitterAuthenticator : AuthenticatorBase { |
| 40 | + readonly string _baseUrl; |
| 41 | + readonly string _clientId; |
| 42 | + readonly string _clientSecret; |
| 43 | + |
| 44 | + public TwitterAuthenticator(string baseUrl, string clientId, string clientSecret) : base("") { |
| 45 | + _baseUrl = baseUrl; |
| 46 | + _clientId = clientId; |
| 47 | + _clientSecret = clientSecret; |
| 48 | + } |
| 49 | + |
| 50 | + protected override async ValueTask<Parameter> GetAuthenticationParameter(string accessToken) { |
| 51 | + var token = string.IsNullOrEmpty(Token) ? await GetToken() : Token; |
| 52 | + return new HeaderParameter(KnownHeaders.Authorization, token); |
34 | 53 | }
|
35 | 54 | }
|
36 | 55 | ```
|
37 | 56 |
|
38 |
| -Next, define a class that maps to the data returned by the API. |
| 57 | +During the first call made by the client using the authenticator, it will find out that the `Token` property is empty. It will then call the `GetToken` function to get the token once, and then will reuse the token going forwards. |
| 58 | + |
| 59 | +Now, we need to include the `GetToken` function to the class: |
39 | 60 |
|
40 | 61 | ```csharp
|
41 |
| -// Call.cs |
42 |
| -public class Call { |
43 |
| - public string Sid { get; set; } |
44 |
| - public DateTime DateCreated { get; set; } |
45 |
| - public DateTime DateUpdated { get; set; } |
46 |
| - public string CallSegmentSid { get; set; } |
47 |
| - public string AccountSid { get; set; } |
48 |
| - public string Called { get; set; } |
49 |
| - public string Caller { get; set; } |
50 |
| - public string PhoneNumberSid { get; set; } |
51 |
| - public int Status { get; set; } |
52 |
| - public DateTime StartTime { get; set; } |
53 |
| - public DateTime EndTime { get; set; } |
54 |
| - public int Duration { get; set; } |
55 |
| - public decimal Price { get; set; } |
56 |
| - public int Flags { get; set; } |
| 62 | +async Task<string> GetToken() { |
| 63 | + var options = new RestClientOptions(_baseUrl); |
| 64 | + using var client = new RestClient(options) { |
| 65 | + Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret), |
| 66 | + }; |
| 67 | + |
| 68 | + var request = new RestRequest("oauth2/token") |
| 69 | + .AddParameter("grant_type", "client_credentials"); |
| 70 | + var response = await client.PostAsync<TokenResponse>(request); |
| 71 | + return $"{response!.TokenType} {response!.AccessToken}"; |
57 | 72 | }
|
58 | 73 | ```
|
59 | 74 |
|
60 |
| -Then add a method to query the API for the details of a specific Call resource. |
| 75 | +As we need to make a call to the token endpoint, we need our own, short-lived instance of `RestClient`. Unlike the actual Twitter client, it will use the `HttpBasicAuthenticator` to send API key and secret as username and password. The client is then gets disposed as we only use it once. |
61 | 76 |
|
62 |
| -```csharp |
63 |
| -// TwilioApi.cs, GetCall method of TwilioApi class |
64 |
| -public Task<Call> GetCall(string callSid) { |
65 |
| - var request = new RestRequest("Accounts/{AccountSid}/Calls/{CallSid}"); |
66 |
| - request.RootElement = "Call"; |
67 |
| - request.AddParameter("CallSid", callSid, ParameterType.UrlSegment); |
| 77 | +Here we add a POST parameter `grant_type` with `client_credentials` as its value. At the moment, it's the only supported value. |
| 78 | + |
| 79 | +The POST request will use the `application/x-www-form-urlencoded` content type by default. |
| 80 | + |
| 81 | +### API client |
68 | 82 |
|
69 |
| - return _client.GetAsync<Call>(request); |
| 83 | +Now, we can start creating the API client itself. Here we start with a single function that retrieves one Twitter user. Let's being by defining the API client interface: |
| 84 | + |
| 85 | +```csharp |
| 86 | +public interface ITwitterClient { |
| 87 | + Task<TwitterUser> GetUser(string user); |
70 | 88 | }
|
71 | 89 | ```
|
72 | 90 |
|
73 |
| -There's some magic here that RestSharp takes care of, so you don't have to. |
| 91 | +As the function returns a `TwitterUser` instance, we need to define it as a model: |
| 92 | + |
| 93 | +```csharp |
| 94 | +public record TwitterUser(string Id, string Name, string Username); |
| 95 | +``` |
74 | 96 |
|
75 |
| -The API returns XML, which is automatically detected and deserialized to the Call object using the default `XmlDeserializer`. |
76 |
| -By default, a call is made via a GET HTTP request. You can change this by setting the `Method` property of `RestRequest` |
77 |
| -or specifying the method in the constructor when creating an instance (covered below). |
78 |
| -Parameters of type `UrlSegment` have their values injected into the URL based on a matching token name existing in the Resource property value. |
79 |
| -`AccountSid` is set in `TwilioApi.Execute` because it is common to every request. |
80 |
| -We specify the name of the root element to start deserializing from. In this case, the XML returned is `<Response><Call>...</Call></Response>` and since the Response element itself does not contain any information relevant to our model, we start the deserializing one step down the tree. |
| 97 | +When that is done, we can implement the interface and add all the necessary code blocks to get a working API client. |
81 | 98 |
|
82 |
| -You can also make POST (and PUT/DELETE/HEAD/OPTIONS) requests: |
| 99 | +The client class needs the following: |
| 100 | +- A constructor, which accepts API credentials to be passed to the authenticator |
| 101 | +- A wrapped `RestClient` instance with Twitter API base URI pre-configured |
| 102 | +- The `TwitterAuthenticator` that we created previously as the client authenticator |
| 103 | +- The actual function to get the user |
83 | 104 |
|
84 | 105 | ```csharp
|
85 |
| -// TwilioApi.cs, method of TwilioApi class |
86 |
| -public Task<Call> InitiateOutboundCall(CallOptions options) { |
87 |
| - var request = new RestRequest("Accounts/{AccountSid}/Calls") { |
88 |
| - RootElement = "Calls" |
| 106 | +public class TwitterClient : ITwitterClient, IDisposable { |
| 107 | + readonly RestClient _client; |
| 108 | + |
| 109 | + public TwitterClient(string apiKey, string apiKeySecret) { |
| 110 | + var options = new RestClientOptions("https://api.twitter.com/2"); |
| 111 | + |
| 112 | + _client = new RestClient(options) { |
| 113 | + Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret) |
| 114 | + }; |
89 | 115 | }
|
90 |
| - .AddParameter("Caller", options.Caller) |
91 |
| - .AddParameter("Called", options.Called) |
92 |
| - .AddParameter("Url", options.Url); |
93 | 116 |
|
94 |
| - if (options.Method.HasValue) request.AddParameter("Method", options.Method); |
95 |
| - if (options.SendDigits.HasValue()) request.AddParameter("SendDigits", options.SendDigits); |
96 |
| - if (options.IfMachine.HasValue) request.AddParameter("IfMachine", options.IfMachine.Value); |
97 |
| - if (options.Timeout.HasValue) request.AddParameter("Timeout", options.Timeout.Value); |
| 117 | + public async Task<TwitterUser> GetUser(string user) { |
| 118 | + var response = await _client.GetJsonAsync<TwitterSingleObject<TwitterUser>>( |
| 119 | + "users/by/username/{user}", |
| 120 | + new { user } |
| 121 | + ); |
| 122 | + return response!.Data; |
| 123 | + } |
98 | 124 |
|
99 |
| - return _client.PostAsync<Call>(request); |
| 125 | + record TwitterSingleObject<T>(T Data); |
| 126 | + |
| 127 | + public void Dispose() { |
| 128 | + _client?.Dispose(); |
| 129 | + GC.SuppressFinalize(this); |
| 130 | + } |
100 | 131 | }
|
101 | 132 | ```
|
102 | 133 |
|
103 |
| -This example also demonstrates RestSharp's lightweight validation helpers. |
104 |
| -These helpers allow you to verify before making the request that the values submitted are valid. |
105 |
| -Read more about Validation here. |
| 134 | +Couple of things that don't fall to the "basics" list. |
| 135 | +- The API client class needs to be disposable, so it can dispose the wrapped `HttpClient` instance |
| 136 | +- Twitter API returns wrapped models. In this case we use the `TwitterSingleObject` wrapper, in other methods you'd need a similar object with `T[] Data` to accept collections |
106 | 137 |
|
107 |
| -All the values added via `AddParameter` in this example will be submitted as a standard encoded form, |
108 |
| -similar to a form submission made via a web page. If this were a GET-style request (GET/DELETE/OPTIONS/HEAD), |
109 |
| -the parameter values would be submitted via the query string instead. You can also add header and cookie |
110 |
| -parameters with `AddParameter`. To add all properties for an object as parameters, use `AddObject`. |
111 |
| -To add a file for upload, use `AddFile` (the request will be sent as a multipart encoded form). |
112 |
| -To include a request body like XML or JSON, use `AddXmlBody` or `AddJsonBody`. |
| 138 | +You can find the full example code in [this gist](https://gist.github.com/alexeyzimarev/62d77bb25d7aa5bb4b9685461f8aabdd). |
| 139 | + |
| 140 | +Such a client can and should be used _as a singleton_, as it's thread-safe and authentication-aware. If you make it a transient dependency, you'll keep bombarding Twitter with token requests and effectively half your request limit. |
113 | 141 |
|
114 | 142 | ## Request Parameters
|
115 | 143 |
|
@@ -160,6 +188,15 @@ If you have `GetOrPost` parameters as well, they will overwrite the `RequestBody
|
160 | 188 |
|
161 | 189 | We recommend using `AddJsonBody` or `AddXmlBody` methods instead of `AddParameter` with type `BodyParameter`. Those methods will set the proper request type and do the serialization work for you.
|
162 | 190 |
|
| 191 | +#### AddStringBody |
| 192 | + |
| 193 | +If you have a pre-serialized payload like a JSON string, you can use `AddStringBody` to add it as a body parameter. You need to specify the content type, so the remote endpoint knows what to do with the request body. For example: |
| 194 | + |
| 195 | +```csharp |
| 196 | +const json = "{ data: { foo: \"bar\" } }"; |
| 197 | +request.AddStringBody(json, ContentType.Json); |
| 198 | +``` |
| 199 | + |
163 | 200 | #### AddJsonBody
|
164 | 201 |
|
165 | 202 | When you call `AddJsonBody`, it does the following for you:
|
|
0 commit comments