Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 49 additions & 12 deletions mustache-templates/csharp/ApiClient.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ namespace {{packageName}}.Client
{{>visibility}} partial class ApiClient : ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
{
private readonly string _baseUrl;
private RestClient _restClient;
private readonly object _restClientLock = new object();

/// <summary>
/// Specifies the settings on a <see cref="JsonSerializer" /> object.
Expand Down Expand Up @@ -436,6 +438,14 @@ namespace {{packageName}}.Client
}
}

if (options.Cookies != null && options.Cookies.Count > 0)
{
foreach (var cookie in options.Cookies)
{
request.AddCookie(cookie.Name, cookie.Value, cookie.Path ?? "/", cookie.Domain ?? "");
}
}

StopTiming(debugModeEnabled, stopwatch, "Request construction", method.ToString(), path);

return request;
Expand Down Expand Up @@ -492,6 +502,24 @@ namespace {{packageName}}.Client
return transformed;
}

/// <summary>
/// Gets or creates a shared RestClient for the given options (lazy init, thread-safe).
/// Used to avoid socket exhaustion by reusing one client per ApiClient instance.
/// </summary>
private RestClient GetOrCreateRestClient(RestClientOptions clientOptions, IReadableConfiguration configuration)
{
if (_restClient != null)
return _restClient;
lock (_restClientLock)
{
if (_restClient != null)
return _restClient;
_restClient = new RestClient(clientOptions,
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration)));
return _restClient;
}
}

/// <summary>
/// Executes the HTTP request for the current service.
/// Based on functions received it can be async or sync.
Expand Down Expand Up @@ -534,8 +562,19 @@ namespace {{packageName}}.Client
}

{{/hasOAuthMethods}}
using (RestClient client = new RestClient(clientOptions,
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration))))
RestClient client;
bool useSharedClient = (baseUrl == _baseUrl);
if (useSharedClient)
{
client = GetOrCreateRestClient(clientOptions, configuration);
}
else
{
client = new RestClient(clientOptions,
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration)));
}

try
{
InterceptRequest(request);

Expand Down Expand Up @@ -602,6 +641,13 @@ namespace {{packageName}}.Client
}
return result;
}
finally
{
if (!useSharedClient)
{
client?.Dispose();
}
}
}

private async Task<RestResponse<T>> DeserializeRestResponseFromPolicyAsync<T>(RestClient client, RestRequest request, PolicyResult<RestResponse> policyResult, bool debugModeEnabled = false, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -629,16 +675,7 @@ namespace {{packageName}}.Client
{
Action<RestClientOptions> setOptions = (clientOptions) =>
{
var cookies = new CookieContainer();

if (options.Cookies != null && options.Cookies.Count > 0)
{
foreach (var cookie in options.Cookies)
{
cookies.Add(new Cookie(cookie.Name, cookie.Value));
}
}
clientOptions.CookieContainer = cookies;
// Cookies are added per-request in NewRequest to avoid mutating shared RestClient
};

Func<RestClient, Task<RestResponse<T>>> getResponse = (client) =>
Expand Down
3 changes: 2 additions & 1 deletion mustache-templates/csharp/README.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ This SDK is auto-generated (except for tests), so changes made here will be over
{{/authMethods}}

## 📅 Changelog

- **2026-03-16** - `9.0.2`
- Fixed: HttpClient/RestClient is no longer disposed after every request; a single shared instance per ApiClient is used to prevent socket exhaustion.
- **2026-03-05** - `9.0.1`
- Added debug mode to measure request timings
- **2025-12-14** - `9.0.0`
Expand Down
Loading