Skip to content

Commit 4287eaf

Browse files
Adding client-level cookie container (#2042)
* Adding client-level cookie container and use it with the new way to add cookies to the request headers * Addressing review * Added more info about cookies and default parameters to the docs
1 parent 159c8a7 commit 4287eaf

File tree

5 files changed

+122
-33
lines changed

5 files changed

+122
-33
lines changed

docs/usage.md

+91-12
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,30 @@ services.AddSingleton<ITwitterClient>(
150150
);
151151
```
152152

153+
### Simple factory
154+
155+
Another way to create the client instance is to use a simple client factory. The factory will use the `BaseUrl` property of the client options to cache `HttpClient` instances. Every distinct base URL will get its own `HttpClient` instance. Other options don't affect the caching. Therefore, if you use different options for the same base URL, you'll get the same `HttpClient` instance, which will not be configured with the new options. Options that aren't applied _after_ the first client instance is created are:
156+
157+
* `Credentials`
158+
* `UseDefaultCredentials`
159+
* `AutomaticDecompression`
160+
* `PreAuthenticate`
161+
* `FollowRedirects`
162+
* `RemoteCertificateValidationCallback`
163+
* `ClientCertificates`
164+
* `MaxRedirects`
165+
* `MaxTimeout`
166+
* `UserAgent`
167+
* `Expect100Continue`
168+
169+
Constructor parameters to configure the `HttpMessageHandler` and default `HttpClient` headers configuration are also ignored for the cached instance as the factory only configures the handler once.
170+
171+
You need to set the `useClientFactory` parameter to `true` in the `RestClient` constructor to enable the factory.
172+
173+
```csharp
174+
var client = new RestClient("https://api.twitter.com/2", true);
175+
```
176+
153177
## Create a request
154178

155179
Before making a request using `RestClient`, you need to create a request instance:
@@ -166,9 +190,23 @@ var request = new RestRequest(resource, Method.Post);
166190

167191
After you've created a `RestRequest`, you can add parameters to it. Below, you can find all the parameter types supported by RestSharp.
168192

169-
### Http Header
193+
### Headers
194+
195+
Adds the header parameter as an HTTP header that is sent along with the request. The header name is the parameter's name and the header value is the value.
196+
197+
You can use one of the following request methods to add a header parameter:
198+
199+
```csharp
200+
AddHeader(string name, string value);
201+
AddHeader<T>(string name, T value); // value will be converted to string
202+
AddOrUpdateHeader(string name, string value); // replaces the header if it already exists
203+
```
204+
205+
You can also add header parameters to the client, and they will be added to every request made by the client. This is useful for adding authentication headers, for example.
170206

171-
Adds the parameter as an HTTP header that is sent along with the request. The header name is the parameter's name and the header value is the value.
207+
```csharp
208+
client.AddDefaultHeader(string name, string value);
209+
```
172210

173211
::: warning Content-Type
174212
RestSharp will use the correct content type by default. Avoid adding the `Content-Type` header manually to your requests unless you are absolutely sure it is required. You can add a custom content type to the [body parameter](#request-body) itself.
@@ -191,6 +229,14 @@ Content-Disposition: form-data; name="parameterName"
191229
ParameterValue
192230
```
193231

232+
You can also add `GetOrPost` parameter as a default parameter to the client. This will add the parameter to every request made by the client.
233+
234+
```csharp
235+
client.AddDefaultParameter("foo", "bar");
236+
```
237+
238+
It will work the same way as request parameters, except that it will be added to every request.
239+
194240
#### AddObject
195241

196242
You can avoid calling `AddParameter` multiple times if you collect all the parameters in an object, and then use `AddObject`.
@@ -241,6 +287,26 @@ var request = new RestRequest("health/{entity}/status")
241287

242288
When the request executes, RestSharp will try to match any `{placeholder}` with a parameter of that name (without the `{}`) and replace it with the value. So the above code results in `health/s2/status` being the url.
243289

290+
You can also add `UrlSegment` parameter as a default parameter to the client. This will add the parameter to every request made by the client.
291+
292+
```csharp
293+
client.AddDefaultUrlSegment("foo", "bar");
294+
```
295+
296+
### Cookies
297+
298+
You can add cookies to a request using the `AddCookie` method:
299+
300+
```csharp
301+
request.AddCookie("foo", "bar");
302+
```
303+
304+
RestSharp will add cookies from the request as cookie headers and then extract the matching cookies from the response. You can observe and extract response cookies using the `RestResponse.Cookies` properties, which has the `CookieCollection` type.
305+
306+
However, the usage of a default URL segment parameter is questionable as you can just include the parameter value to the base URL of the client. There is, however, a `CookieContainer` instance on the request level. You can either assign the pre-populated container to `request.CookieContainer`, or let the container be created by the request when you call `AddCookie`. Still, the container is only used to extract all the cookies from it and create cookie headers for the request instead of using the container directly. It's because the cookie container is normally configured on the `HttpClientHandler` level and cookies are shared between requests made by the same client. In most of the cases this behaviour can be harmful.
307+
308+
If your use case requires sharing cookies between requests made by the client instance, you can use the client-level `CookieContainer`, which you must provide as the options' property. You can add cookies to the container using the container API. No response cookies, however, would be auto-added to the container, but you can do it in code by getting cookies from the `Cookes` property of the response and adding them to the client-level container available via `IRestClient.Options.CookieContainer` property.
309+
244310
### Request Body
245311

246312
RestSharp supports multiple ways to add a request body:
@@ -252,6 +318,8 @@ We recommend using `AddJsonBody` or `AddXmlBody` methods instead of `AddParamete
252318

253319
When you make a `POST`, `PUT` or `PATCH` request and added `GetOrPost` [parameters](#get-or-post), RestSharp will send them as a URL-encoded form request body by default. When a request also has files, it will send a `multipart/form-data` request. You can also instruct RestSharp to send the body as `multipart/form-data` by setting the `AlwaysMultipartFormData` property to `true`.
254320

321+
It is not possible to add client-level default body parameters.
322+
255323
#### AddStringBody
256324

257325
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:
@@ -322,6 +390,14 @@ To do so, set the `encode` argument to `false` when adding the parameter:
322390
request.AddQueryParameter("foo", "bar/fox", false);
323391
```
324392

393+
You can also add a query string parameter as a default parameter to the client. This will add the parameter to every request made by the client.
394+
395+
```csharp
396+
client.AddDefaultQueryParameter("foo", "bar");
397+
```
398+
399+
The line above will result in all the requests made by that client instance to have `foo=bar` in the query string for all the requests made by that client.
400+
325401
## Making a call
326402

327403
Once you've added all the parameters to your `RestRequest`, you are ready to make a request.
@@ -354,7 +430,7 @@ Task<RestResponse<T>> ExecutePostAsync<T>(RestRequest request, CancellationToken
354430
Task<RestResponse<T>> ExecutePutAsync<T>(RestRequest request, CancellationToken cancellationToken)
355431
```
356432

357-
All the overloads that return `RestResponse` or `RestResponse<T>` don't throw an exception if the server returns an error. Read more about it [here](error-handling.md).
433+
All the overloads with names starting with `Execute` don't throw an exception if the server returns an error. Read more about it [here](error-handling.md).
358434

359435
If you just need a deserialized response, you can use one of the extensions:
360436

@@ -369,6 +445,17 @@ Task<T> DeleteAsync<T>(RestRequest request, CancellationToken cancellationToken)
369445

370446
Those extensions will throw an exception if the server returns an error, as there's no other way to float the error back to the caller.
371447

448+
The `IRestClient` interface also has extensions for making requests without deserialization, which throw an exception if the server returns an error even if the client is configured to not throw exceptions.
449+
450+
```csharp
451+
Task<RestResponse> GetAsync(RestRequest request, CancellationToken cancellationToken)
452+
Task<RestResponse> PostAsync(RestRequest request, CancellationToken cancellationToken)
453+
Task<RestResponse> PutAsync(RestRequest request, CancellationToken cancellationToken)
454+
Task<RestResponse> HeadAsync(RestRequest request, CancellationToken cancellationToken)
455+
Task<RestResponse> PatchAsync(RestRequest request, CancellationToken cancellationToken)
456+
Task<RestResponse> DeleteAsync(RestRequest request, CancellationToken cancellationToken)
457+
```
458+
372459
### JSON requests
373460

374461
To make a simple `GET` call and get a deserialized JSON response with a pre-formed resource string, use this:
@@ -474,15 +561,7 @@ One way of doing it is to use `RestClient` constructors that accept an instance
474561
- `UserAgent` will be set if the `User-Agent` header is not set on the `HttpClient` instance already.
475562
- `Expect100Continue`
476563

477-
Another option is to use a simple HTTP client factory. It is a static factory, which holds previously instantiated `HttpClient` instances. It can be used to create `RestClient` instances that share the same `HttpClient` instance. The cache key is the `BaseUrl` provided in the options. When you opt-in to use the factory and don't set `BaseUrl`, the `RestClient` constructor will crash.
478-
479-
```csharp
480-
var client = new RestClient(new Uri("https://example.org/api"), useClientFactory: true);
481-
```
482-
483-
::: warning
484-
Note that the `RestClient` constructor will not reconfigure the `HttpClient` instance if it's already in the cache. Therefore, you should not try using the factory when providing different options for the same base URL.
485-
:::
564+
Another option is to use a simple HTTP client factory as described [above](#simple-factory).
486565

487566
## Blazor support
488567

docs/v107/README.md

+5-9
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,12 @@ Below, you can find members of `IRestClient` and `IRestRequest` with their corre
224224

225225
| `IRestClient` member | Where is it now? |
226226
|:------------------------------------------------------------------------------------------------|:-----------------------------------|
227-
| `CookieContainer` | `RestClient` |
227+
| `CookieContainer` | `RestClientOptions` |
228228
| `AutomaticDecompression` | `RestClientOptions`, changed type |
229229
| `MaxRedirects` | `RestClientOptions` |
230230
| `UserAgent` | `RestClientOptions` |
231231
| `Timeout` | `RestClientOptions`, `RestRequest` |
232-
| `Authenticator` | `RestClient` |
232+
| `Authenticator` | `RestClientOptions` |
233233
| `BaseUrl` | `RestClientOptions` |
234234
| `Encoding` | `RestClientOptions` |
235235
| `ThrowOnDeserializationError` | `RestClientOptions` |
@@ -249,12 +249,8 @@ Below, you can find members of `IRestClient` and `IRestRequest` with their corre
249249
| `ReadWriteTimeout` | Not supported |
250250
| `UseSynchronizationContext` | Not supported |
251251
| `DefaultParameters` | `RestClient` |
252-
| `UseSerializer(Func<IRestSerializer> serializerFactory)` | `RestClient` |
253-
| `UseSerializer<T>()` | `RestClient` |
254-
| `Deserialize<T>(IRestResponse response)` | `RestClient` |
255-
| `BuildUri(IRestRequest request)` | `RestClient` |
256-
| `UseUrlEncoder(Func<string, string> encoder)` | Extension |
257-
| `UseQueryEncoder(Func<string, Encoding, string> queryEncoder)` | Extension |
252+
| `Deserialize<T>(IRestResponse response)` | `RestSerializers` |
253+
| `BuildUri(IRestRequest request)` | Extension |
258254
| `ExecuteAsync<T>(IRestRequest request, CancellationToken cancellationToken)` | `RestClient` |
259255
| `ExecuteAsync<T>(IRestRequest request, Method httpMethod, CancellationToken cancellationToken)` | Extension |
260256
| `ExecuteAsync(IRestRequest request, Method httpMethod, CancellationToken cancellationToken)` | Extension |
@@ -272,7 +268,7 @@ Below, you can find members of `IRestClient` and `IRestRequest` with their corre
272268
| `ExecuteAsPost(IRestRequest request, string httpMethod)` | Deprecated |
273269
| `ExecuteAsGet<T>(IRestRequest request, string httpMethod)` | Deprecated |
274270
| `ExecuteAsPost<T>(IRestRequest request, string httpMethod)` | Deprecated |
275-
| `BuildUriWithoutQueryParameters(IRestRequest request)` | Removed |
271+
| `BuildUriWithoutQueryParameters(IRestRequest request)` | Extension |
276272
| `ConfigureWebRequest(Action<HttpWebRequest> configurator)` | Removed |
277273
| `AddHandler(string contentType, Func<IDeserializer> deserializerFactory)` | Removed |
278274
| `RemoveHandler(string contentType)` | Removed |

src/RestSharp/Options/RestClientOptions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba
136136
/// </summary>
137137
public string? BaseHost { get; set; }
138138

139+
/// <summary>
140+
/// Custom cookie container to be used for requests. RestSharp will not assign the container to the message handler,
141+
/// but will fetch cookies from it and set them on the request.
142+
/// </summary>
143+
public CookieContainer? CookieContainer { get; set; }
144+
139145
/// <summary>
140146
/// Maximum request duration in milliseconds. When the request timeout is specified using <seealso cref="RestRequest.Timeout"/>,
141147
/// the lowest value between the client timeout and request timeout will be used.

src/RestSharp/RestClient.Async.cs

+10-5
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,16 @@ async Task<HttpResponse> ExecuteRequestAsync(RestRequest request, CancellationTo
106106
// Make sure we have a cookie container if not provided in the request
107107
var cookieContainer = request.CookieContainer ??= new CookieContainer();
108108

109-
var headers = new RequestHeaders();
110-
headers.AddHeaders(request.Parameters);
111-
headers.AddHeaders(DefaultParameters);
112-
headers.AddAcceptHeader(AcceptedContentTypes);
113-
headers.AddCookieHeaders(cookieContainer, url);
109+
var headers = new RequestHeaders()
110+
.AddHeaders(request.Parameters)
111+
.AddHeaders(DefaultParameters)
112+
.AddAcceptHeader(AcceptedContentTypes)
113+
.AddCookieHeaders(cookieContainer, url);
114+
115+
if (Options.CookieContainer != null) {
116+
headers.AddCookieHeaders(Options.CookieContainer, url);
117+
}
118+
114119
message.AddHeaders(headers);
115120

116121
if (request.OnBeforeRequest != null) await request.OnBeforeRequest(message).ConfigureAwait(false);

src/RestSharp/RestClient.Extensions.Params.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@
1313
// limitations under the License.
1414
//
1515

16+
using System.Net;
17+
using System.Text;
18+
1619
namespace RestSharp;
1720

1821
public static partial class RestClientExtensions {
1922
/// <summary>
2023
/// Add a parameter to use on every request made with this client instance
2124
/// </summary>
22-
/// <param name="client">RestClient instance</param>
23-
/// <param name="parameter">Parameter to add</param>
25+
/// <param name="client"><see cref="RestClient"/> instance</param>
26+
/// <param name="parameter"><see cref="Parameter"/> to add</param>
2427
/// <returns></returns>
2528
public static IRestClient AddDefaultParameter(this IRestClient client, Parameter parameter) {
2629
client.DefaultParameters.AddParameter(parameter);
@@ -31,7 +34,7 @@ public static IRestClient AddDefaultParameter(this IRestClient client, Parameter
3134
/// Adds a default HTTP parameter (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT)
3235
/// Used on every request made by this client instance
3336
/// </summary>
34-
/// <param name="client"><see cref="RestClientOptions"/> instance</param>
37+
/// <param name="client"><see cref="RestClient"/> instance</param>
3538
/// <param name="name">Name of the parameter</param>
3639
/// <param name="value">Value of the parameter</param>
3740
/// <returns>This request</returns>
@@ -46,7 +49,7 @@ public static IRestClient AddDefaultParameter(this IRestClient client, string na
4649
/// - RequestBody: Used by AddBody() (not recommended to use directly)
4750
/// Used on every request made by this client instance
4851
/// </summary>
49-
/// <param name="client"><see cref="RestClientOptions"/> instance</param>
52+
/// <param name="client"><see cref="RestClient"/> instance</param>
5053
/// <param name="name">Name of the parameter</param>
5154
/// <param name="value">Value of the parameter</param>
5255
/// <param name="type">The type of parameter to add</param>
@@ -57,7 +60,7 @@ public static IRestClient AddDefaultParameter(this IRestClient client, string na
5760
/// <summary>
5861
/// Adds a default header to the RestClient. Used on every request made by this client instance.
5962
/// </summary>
60-
/// <param name="client"><see cref="RestClientOptions"/> instance</param>
63+
/// <param name="client"><see cref="RestClient"/> instance</param>
6164
/// <param name="name">Name of the header to add</param>
6265
/// <param name="value">Value of the header to add</param>
6366
/// <returns></returns>
@@ -79,7 +82,7 @@ public static IRestClient AddDefaultHeaders(this IRestClient client, Dictionary<
7982
/// <summary>
8083
/// Adds a default URL segment parameter to the RestClient. Used on every request made by this client instance.
8184
/// </summary>
82-
/// <param name="client"><see cref="RestClientOptions"/> instance</param>
85+
/// <param name="client"><see cref="RestClient"/> instance</param>
8386
/// <param name="name">Name of the segment to add</param>
8487
/// <param name="value">Value of the segment to add</param>
8588
/// <returns></returns>
@@ -89,7 +92,7 @@ public static IRestClient AddDefaultUrlSegment(this IRestClient client, string n
8992
/// <summary>
9093
/// Adds a default URL query parameter to the RestClient. Used on every request made by this client instance.
9194
/// </summary>
92-
/// <param name="client"><see cref="RestClientOptions"/> instance</param>
95+
/// <param name="client"><see cref="RestClient"/> instance</param>
9396
/// <param name="name">Name of the query parameter to add</param>
9497
/// <param name="value">Value of the query parameter to add</param>
9598
/// <returns></returns>

0 commit comments

Comments
 (0)