Description
In RestClient we have some public properties that you can modify after the client is constructed, specifically:
internal Func<string, string> Encode { get; set; } = s => s.UrlEncode();
internal Func<string, Encoding, string> EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding)!;
public IAuthenticator? Authenticator { get; set; }
They can all be access via RestClientExtensions:
public static partial class RestClientExtensions {
[PublicAPI]
public static RestResponse<T> Deserialize<T>(this RestClient client, RestResponse response) => client.Deserialize<T>(response.Request!, response);
/// <summary>
/// Allows to use a custom way to encode URL parameters
/// </summary>
/// <param name="client"></param>
/// <param name="encoder">A delegate to encode URL parameters</param>
/// <example>client.UseUrlEncoder(s => HttpUtility.UrlEncode(s));</example>
/// <returns></returns>
public static RestClient UseUrlEncoder(this RestClient client, Func<string, string> encoder) => client.With(x => x.Encode = encoder);
/// <summary>
/// Allows to use a custom way to encode query parameters
/// </summary>
/// <param name="client"></param>
/// <param name="queryEncoder">A delegate to encode query parameters</param>
/// <example>client.UseUrlEncoder((s, encoding) => HttpUtility.UrlEncode(s, encoding));</example>
/// <returns></returns>
public static RestClient UseQueryEncoder(this RestClient client, Func<string, Encoding, string> queryEncoder)
=> client.With(x => x.EncodeQuery = queryEncoder);
/// <summary>
/// Adds cookie to the <seealso cref="HttpClient"/> cookie container.
/// </summary>
/// <param name="client"></param>
/// <param name="name">Cookie name</param>
/// <param name="value">Cookie value</param>
/// <param name="path">Cookie path</param>
/// <param name="domain">Cookie domain, must not be an empty string</param>
/// <returns></returns>
public static RestClient AddCookie(this RestClient client, string name, string value, string path, string domain) {
lock (client.CookieContainer) {
client.CookieContainer.Add(new Cookie(name, value, path, domain));
}
return client;
}
public static RestClient UseAuthenticator(this RestClient client, IAuthenticator authenticator)
=> client.With(x => x.Authenticator = authenticator);
}
Unfortunately because RestClient is no longer an instance per request, it is expected to be shared across multiple threads all calling the same API endpoints (single instance per API endpoint). Basically anything configured on the RestClient instance needs to be set up when the instance is created and NOT touched later on. But these are all publicly accessible which indicates to the user they can adjust them for each request,
The Url and Query encoders are a real problem because if two threads change them, you will get unexpected results. The same goes for the Authenticator. If someone changes the Authenticator for a client instance in two different threads, it is going to also cause unexpected results. It would be quite common to use say HttpBasicAuthenticator() to pass in different credentials on each request, but with the current design there is actually no way to do that.
And for cookies, since HttpClient requires the cookie container to be configured on the client, all cookies are going to be a) shared and b) accumulate on every API call, which is really bad. But we can't simply clear the cookie container on every request, because it's a shared resource used by all concurrent requests. Since HttpClient does not support setting a separate cookie container on every request, I suggest the solution is to make the cookies per request, and rendering the cookies into the headers as part of submitting the query as discussed here:
Since RestClient is expected to be used in a single instance fashion, the API surface area for it needs to be changed to reflect that and not allow things to be reconfigured on the fly after an instance is constructed.
So my recommendations are to remove all those properties out of the RestClient implementation and put them into RestRequest, so they can be setup and managed on a per request basis.