diff --git a/docs/docs/usage/client.md b/docs/docs/usage/client.md index 97af9fce5..6e95e130a 100644 --- a/docs/docs/usage/client.md +++ b/docs/docs/usage/client.md @@ -48,11 +48,12 @@ Another way to create the client instance is to use a simple client factory. The * `ClientCertificates` * `MaxRedirects` * `Timeout` -* `UserAgent` * `Expect100Continue` 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. +The `UserAgent` option is not affected by caching: it is added to each `RestClient` instance's default parameters and applied per request, so every client gets its own `User-Agent` header even when the underlying `HttpClient` is reused. + You need to set the `useClientFactory` parameter to `true` in the `RestClient` constructor to enable the factory. ```csharp diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index 9ab456975..9762e63ec 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -76,6 +76,8 @@ public RestClient( ConfigureSerializers(configureSerialization); Options = new(options); DefaultParameters = new(Options); + // Must run per RestClient instance, not inside GetClient: the factory may return a cached HttpClient without invoking GetClient + ConfigureDefaultParameters(options); if (useClientFactory) { _disposeHttpClient = false; @@ -97,7 +99,6 @@ HttpClient GetClient() { // We will use Options.Timeout in ExecuteAsInternalAsync method httpClient.Timeout = Timeout.InfiniteTimeSpan; - ConfigureDefaultParameters(options); configureDefaultHeaders?.Invoke(httpClient.DefaultRequestHeaders); return httpClient; } @@ -185,10 +186,7 @@ public RestClient( var opt = options ?? new RestClientOptions(); Options = new(opt); DefaultParameters = new(Options); - - if (options != null) { - ConfigureDefaultParameters(options); - } + ConfigureDefaultParameters(opt); } /// diff --git a/test/RestSharp.Tests/RestClientTests.cs b/test/RestSharp.Tests/RestClientTests.cs index 2c82278f1..ea4f9a34a 100644 --- a/test/RestSharp.Tests/RestClientTests.cs +++ b/test/RestSharp.Tests/RestClientTests.cs @@ -124,6 +124,47 @@ public void ConfigureDefaultParameters_sets_user_agent_given_httpClient_instance Assert.Empty(httpClient.DefaultRequestHeaders.UserAgent); } + [Fact] + public void ConfigureDefaultParameters_sets_user_agent_using_factory_twice() { + // arrange + const string expectedAgentString = "Agent/1.0"; + + // The base URL is unique to this test to keep the process-wide factory cache isolated from other tests + var clientOptions = new RestClientOptions { BaseUrl = new Uri("https://localhost:8888"), UserAgent = expectedAgentString }; + + // act + using var firstRestClient = new RestClient(clientOptions, useClientFactory: true); + using var secondRestClient = new RestClient(clientOptions, useClientFactory: true); + + //assert + secondRestClient.HttpClient.Should().BeSameAs(firstRestClient.HttpClient, "the regression only manifests on a factory cache hit"); + AssertHasUserAgent(firstRestClient); + AssertHasUserAgent(secondRestClient); + return; + + static void AssertHasUserAgent(RestClient restClient) + => Assert.Single( + restClient.DefaultParameters, + parameter => parameter is { Type: ParameterType.HttpHeader, Name: KnownHeaders.UserAgent, Value: expectedAgentString } + ); + } + + [Fact] + public void ConfigureDefaultParameters_sets_user_agent_given_httpClient_and_null_options() { + // arrange + var httpClient = new HttpClient(); + + // act + using var restClient = new RestClient(httpClient, options: null); + + //assert + Assert.Single( + restClient.DefaultParameters, + parameter => parameter is { Type: ParameterType.HttpHeader, Name: KnownHeaders.UserAgent, Value: string valueAsString } && + valueAsString == new RestClientOptions().UserAgent + ); + } + [Fact] public void Should_not_set_expect_continue_on_shared_http_client_default_headers() { // arrange