diff --git a/src/RestSharp/Extensions/ObjectExtensions.cs b/src/RestSharp/Extensions/ObjectExtensions.cs
new file mode 100644
index 000000000..5a596d8ba
--- /dev/null
+++ b/src/RestSharp/Extensions/ObjectExtensions.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Globalization;
+
+namespace RestSharp.Extensions;
+
+static class ObjectExtensions {
+ ///
+ /// Converts a value to its string representation using the specified culture for IFormattable types.
+ ///
+ /// The type of value to convert
+ /// The value to convert
+ /// The culture to use for formatting. If null, uses the current culture.
+ /// String representation using the specified culture, or null if value is null
+ internal static string? ToStringWithCulture(this T value, CultureInfo? culture) => value switch {
+ null => null,
+ IFormattable f => f.ToString(null, culture),
+ _ => value.ToString()
+ };
+}
diff --git a/src/RestSharp/Options/RestClientOptions.cs b/src/RestSharp/Options/RestClientOptions.cs
index d30e34f92..3017fd985 100644
--- a/src/RestSharp/Options/RestClientOptions.cs
+++ b/src/RestSharp/Options/RestClientOptions.cs
@@ -13,6 +13,7 @@
// limitations under the License.
//
+using System.Globalization;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Reflection;
@@ -230,4 +231,10 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba
/// Custom function to encode a string for use in a URL query.
///
public Func EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding)!;
+
+ ///
+ /// Culture to use for formatting IFormattable parameter values. Default is null which uses the current culture.
+ /// Set to to ensure consistent formatting across different locales.
+ ///
+ public CultureInfo? CultureForParameters { get; set; }
}
diff --git a/src/RestSharp/Request/RestRequestExtensions.Culture.cs b/src/RestSharp/Request/RestRequestExtensions.Culture.cs
new file mode 100644
index 000000000..335e88aaf
--- /dev/null
+++ b/src/RestSharp/Request/RestRequestExtensions.Culture.cs
@@ -0,0 +1,71 @@
+// Copyright (c) .NET Foundation and Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using RestSharp.Extensions;
+
+namespace RestSharp;
+
+public static partial class RestRequestExtensions {
+ ///
+ /// Adds a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT).
+ /// The value will be converted to string using the culture specified in .
+ ///
+ /// The RestClient instance containing the culture settings
+ /// The request to add the parameter to
+ /// Name of the parameter
+ /// Value of the parameter
+ /// Encode the value or not, default true
+ /// This request
+ public static RestRequest AddParameter(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct
+ => request.AddParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode);
+
+ ///
+ /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT).
+ /// The value will be converted to string using the culture specified in .
+ ///
+ /// The RestClient instance containing the culture settings
+ /// The request to add the parameter to
+ /// Name of the parameter
+ /// Value of the parameter
+ /// Encode the value or not, default true
+ /// This request
+ public static RestRequest AddOrUpdateParameter(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct
+ => request.AddOrUpdateParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode);
+
+ ///
+ /// Adds a query string parameter to the request.
+ /// The value will be converted to string using the culture specified in .
+ ///
+ /// The RestClient instance containing the culture settings
+ /// The request to add the parameter to
+ /// Parameter name
+ /// Parameter value
+ /// Encode the value or not, default true
+ ///
+ public static RestRequest AddQueryParameter(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct
+ => request.AddQueryParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode);
+
+ ///
+ /// Adds a URL segment parameter to the request.
+ /// The value will be converted to string using the culture specified in .
+ ///
+ /// The RestClient instance containing the culture settings
+ /// The request to add the parameter to
+ /// Name of the parameter; must be matching a placeholder in the resource URL as {name}
+ /// Value of the parameter
+ /// Encode the value or not, default true
+ ///
+ public static RestRequest AddUrlSegment(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct
+ => request.AddUrlSegment(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode);
+}
diff --git a/src/RestSharp/RestClient.Extensions.Params.cs b/src/RestSharp/RestClient.Extensions.Params.cs
index cd3bb635c..33531d908 100644
--- a/src/RestSharp/RestClient.Extensions.Params.cs
+++ b/src/RestSharp/RestClient.Extensions.Params.cs
@@ -13,6 +13,8 @@
// limitations under the License.
//
+using RestSharp.Extensions;
+
namespace RestSharp;
public static partial class RestClientExtensions {
@@ -38,6 +40,17 @@ public IRestClient AddDefaultParameter(Parameter parameter) {
public IRestClient AddDefaultParameter(string name, string value)
=> client.AddDefaultParameter(new GetOrPostParameter(name, value));
+ ///
+ /// Adds a default HTTP parameter (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT)
+ /// Used on every request made by this client instance. The value will be formatted using the culture
+ /// specified in .
+ ///
+ /// Name of the parameter
+ /// Value of the parameter
+ /// This request
+ public IRestClient AddDefaultParameter(string name, T value) where T : struct
+ => client.AddDefaultParameter(new GetOrPostParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters)));
+
///
/// Adds a default parameter to the client options. There are four types of parameters:
/// - GetOrPost: Either a QueryString value or encoded form value based on method
@@ -82,6 +95,16 @@ public IRestClient AddDefaultHeaders(Dictionary headers) {
public IRestClient AddDefaultUrlSegment(string name, string value)
=> client.AddDefaultParameter(new UrlSegmentParameter(name, value));
+ ///
+ /// Adds a default URL segment parameter to the RestClient. Used on every request made by this client instance.
+ /// The value will be formatted using the culture specified in .
+ ///
+ /// Name of the segment to add
+ /// Value of the segment to add
+ ///
+ public IRestClient AddDefaultUrlSegment(string name, T value) where T : struct
+ => client.AddDefaultParameter(new UrlSegmentParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters)));
+
///
/// Adds a default URL query parameter to the RestClient. Used on every request made by this client instance.
///
@@ -90,5 +113,23 @@ public IRestClient AddDefaultUrlSegment(string name, string value)
///
public IRestClient AddDefaultQueryParameter(string name, string value)
=> client.AddDefaultParameter(new QueryParameter(name, value));
+
+ ///
+ /// Adds a default URL query parameter to the RestClient. Used on every request made by this client instance.
+ /// The value will be formatted using the culture specified in .
+ ///
+ /// Name of the query parameter to add
+ /// Value of the query parameter to add
+ ///
+ public IRestClient AddDefaultQueryParameter(string name, T value) where T : struct
+ => client.AddDefaultParameter(new QueryParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters)));
+
+ ///
+ /// Formats the value using the culture specified in .
+ ///
+ /// Value to format
+ /// String representation of the value using the client's culture setting
+ public string? FormatValue(T value) where T : struct
+ => value.ToStringWithCulture(client.Options.CultureForParameters);
}
}
diff --git a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs
new file mode 100644
index 000000000..43d5ccf50
--- /dev/null
+++ b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs
@@ -0,0 +1,265 @@
+using System.Globalization;
+
+namespace RestSharp.Tests.Parameters;
+
+public class InvariantCultureParameterTests {
+ [Fact]
+ public void AddParameter_Double_UsesInvariantCulture_WhenConfigured() {
+ // Save original culture
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ // Set a culture that uses comma as decimal separator
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest();
+ request.AddParameter(client, "value", 1.234);
+
+ var parameter = request.Parameters.First();
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddParameter_Double_UsesCurrentCulture_ByDefault() {
+ // Save original culture
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ // Set a culture that uses comma as decimal separator
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var request = new RestRequest();
+ request.AddParameter("value", 1.234);
+
+ var parameter = request.Parameters.First();
+ // Default behavior uses current culture (comma as decimal separator)
+ parameter.Value.Should().Be("1,234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddOrUpdateParameter_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest();
+ request.AddOrUpdateParameter(client, "value", 1.234);
+
+ var parameter = request.Parameters.First();
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddQueryParameter_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest();
+ request.AddQueryParameter(client, "value", 1.234);
+
+ var parameter = request.Parameters.First();
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddUrlSegment_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest("{value}");
+ request.AddUrlSegment(client, "value", 1.234);
+
+ var parameter = request.Parameters.First();
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddDefaultParameter_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ client.AddDefaultParameter("value", 1.234);
+
+ var parameter = client.DefaultParameters.First(p => p.Name == "value");
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddDefaultQueryParameter_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ client.AddDefaultQueryParameter("value", 1.234);
+
+ var parameter = client.DefaultParameters.First(p => p.Name == "value");
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddDefaultUrlSegment_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ client.AddDefaultUrlSegment("value", 1.234);
+
+ var parameter = client.DefaultParameters.First(p => p.Name == "value");
+ parameter.Value.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void FormatValue_Double_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+
+ var formattedValue = client.FormatValue(1.234);
+
+ formattedValue.Should().Be("1.234");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddParameter_Decimal_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest();
+ request.AddParameter(client, "value", 123.456m);
+
+ var parameter = request.Parameters.First();
+ parameter.Value.Should().Be("123.456");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddParameter_Float_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest();
+ request.AddParameter(client, "value", 2.5f);
+
+ var parameter = request.Parameters.First();
+ parameter.Value.Should().Be("2.5");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddParameter_DateTime_UsesInvariantCulture_WhenConfigured() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var dateTime = new DateTime(2024, 12, 25, 10, 30, 0, DateTimeKind.Unspecified);
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var request = new RestRequest();
+ request.AddParameter(client, "date", dateTime);
+
+ var parameter = request.Parameters.First();
+ // DateTime.ToString with InvariantCulture uses MM/dd/yyyy format
+ parameter.Value.Should().Be("12/25/2024 10:30:00");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void AddParameter_Integer_SameValueWithOrWithoutInvariantCulture() {
+ var originalCulture = Thread.CurrentThread.CurrentCulture;
+ try {
+ Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
+
+ var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture };
+ using var client = new RestClient(options);
+ var requestWithInvariant = new RestRequest();
+ requestWithInvariant.AddParameter(client, "value", 12345);
+
+ var requestWithoutInvariant = new RestRequest();
+ requestWithoutInvariant.AddParameter("value", 12345);
+
+ var parameterWithInvariant = requestWithInvariant.Parameters.First();
+ var parameterWithoutInvariant = requestWithoutInvariant.Parameters.First();
+
+ parameterWithInvariant.Value.Should().Be("12345");
+ parameterWithoutInvariant.Value.Should().Be("12345");
+ }
+ finally {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
+
+ [Fact]
+ public void CultureForParameters_DefaultValue_IsNull() {
+ var options = new RestClientOptions();
+ options.CultureForParameters.Should().BeNull();
+ }
+}