diff --git a/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs b/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
index f69f5ff39..56906120f 100644
--- a/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
+++ b/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
@@ -245,7 +245,7 @@ internal static void AddOAuthData(
"Using query parameters in the base URL is not supported for OAuth calls. Consider using AddDefaultQueryParameter instead."
);
- var url = client.BuildUri(request).ToString();
+ var url = client.BuildUriString(request);
var queryStringStart = url.IndexOf('?');
if (queryStringStart != -1) url = url[..queryStringStart];
diff --git a/src/RestSharp/BuildUriExtensions.cs b/src/RestSharp/BuildUriExtensions.cs
index 6bf976244..8fb95563e 100644
--- a/src/RestSharp/BuildUriExtensions.cs
+++ b/src/RestSharp/BuildUriExtensions.cs
@@ -23,18 +23,23 @@ public static class BuildUriExtensions {
///
/// Request instance
///
- public Uri BuildUri(RestRequest request) {
- DoBuildUriValidations(client, request);
+ public Uri BuildUri(RestRequest request) => new(client.BuildUriString(request));
- var (uri, resource) = client.Options.BaseUrl.GetUrlSegmentParamsValues(
- request.Resource,
- client.Options.Encode,
- request.Parameters,
- client.DefaultParameters
- );
- var mergedUri = uri.MergeBaseUrlAndResource(resource);
+ ///
+ /// Builds the URI string for the request. This method returns a string instead of a Uri object
+ /// to preserve unencoded characters when encode=false is specified for query parameters.
+ ///
+ /// Request instance
+ ///
+ [PublicAPI]
+ public string BuildUriString(RestRequest request) {
+ var mergedUri = client.BuildUriWithoutQueryParameters(request);
var query = client.GetRequestQuery(request);
- return mergedUri.AddQueryString(query);
+
+ if (query == null) return mergedUri.AbsoluteUri;
+
+ var separator = mergedUri.AbsoluteUri.Contains('?') ? "&" : "?";
+ return $"{mergedUri.AbsoluteUri}{separator}{query}";
}
///
diff --git a/src/RestSharp/Request/UriExtensions.cs b/src/RestSharp/Request/UriExtensions.cs
index 9cb26ebe8..bc217dff6 100644
--- a/src/RestSharp/Request/UriExtensions.cs
+++ b/src/RestSharp/Request/UriExtensions.cs
@@ -41,10 +41,10 @@ public static Uri MergeBaseUrlAndResource(this Uri? baseUrl, string? resource) {
public static Uri AddQueryString(this Uri uri, string? query) {
if (query == null) return uri;
- var absoluteUri = uri.AbsoluteUri;
- var separator = absoluteUri.Contains('?') ? "&" : "?";
+ var builder = new UriBuilder(uri);
+ builder.Query = builder.Query.Length > 1 ? $"{builder.Query[1..]}&{query}" : query;
- return new($"{absoluteUri}{separator}{query}");
+ return builder.Uri;
}
public static UrlSegmentParamsValues GetUrlSegmentParamsValues(
diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs
index 75c8ac85f..df4558795 100644
--- a/src/RestSharp/RestClient.Async.cs
+++ b/src/RestSharp/RestClient.Async.cs
@@ -111,9 +111,10 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo
using var requestContent = new RequestContent(this, request);
var httpMethod = AsHttpMethod(request.Method);
- var url = this.BuildUri(request);
+ var urlString = this.BuildUriString(request);
+ var url = new Uri(urlString);
- using var message = new HttpRequestMessage(httpMethod, url);
+ using var message = new HttpRequestMessage(httpMethod, urlString);
message.Content = requestContent.BuildContent();
message.Headers.Host = Options.BaseHost;
message.Headers.CacheControl = request.CachePolicy ?? Options.CachePolicy;
diff --git a/test/RestSharp.InteractiveTests/AuthenticationTests.cs b/test/RestSharp.InteractiveTests/AuthenticationTests.cs
index 7e5d81173..72754b1a8 100644
--- a/test/RestSharp.InteractiveTests/AuthenticationTests.cs
+++ b/test/RestSharp.InteractiveTests/AuthenticationTests.cs
@@ -39,8 +39,7 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
request = new($"oauth/authorize?oauth_token={oauthToken}");
- var url = client.BuildUri(request)
- .ToString();
+ var url = client.BuildUriString(request);
Console.WriteLine($"Open this URL in the browser: {url} and complete the authentication.");
Console.Write("Enter the verifier: ");
diff --git a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs
index f61c5a55d..c2038f6a3 100644
--- a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs
+++ b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs
@@ -35,4 +35,17 @@ public async Task Should_not_throw_exception_when_name_is_null() {
await client.ExecuteAsync(request);
}
+
+ [Fact]
+ public async Task Should_not_encode_pipe_character_when_encode_is_false() {
+ using var client = new RestClient(server.Url!);
+
+ var request = new RestRequest("capture");
+ request.AddQueryParameter("ids", "in:001|116", false);
+
+ await client.ExecuteAsync(request);
+
+ var query = _capturer.RawUrl.Split('?')[1];
+ query.Should().Contain("ids=in:001|116");
+ }
}
\ No newline at end of file
diff --git a/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs
index cc9ead4fb..cca83ff9c 100644
--- a/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs
+++ b/test/RestSharp.Tests.Shared/Fixtures/RequestBodyCapturer.cs
@@ -7,6 +7,7 @@ public class RequestBodyCapturer {
public bool HasBody { get; private set; }
public string Body { get; private set; }
public Uri Url { get; private set; }
+ public string RawUrl { get; private set; }
public bool CaptureBody(string content) {
Body = content;
@@ -23,6 +24,7 @@ public bool CaptureHeaders(IDictionary headers) {
}
public bool CaptureUrl(string url) {
+ RawUrl = url;
Url = new(url);
return true;
}
diff --git a/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs b/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs
index 782a7a121..f23f411c5 100644
--- a/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs
+++ b/test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs
@@ -37,7 +37,7 @@ public void Generates_correct_signature_base() {
var requestParameters = _request.Parameters.ToWebParameters().ToArray();
var parameters = new WebPairCollection();
parameters.AddRange(requestParameters);
- var url = _client.BuildUri(_request).ToString();
+ var url = _client.BuildUriString(_request);
_workflow.RequestUrl = url;
var oauthParameters = _workflow.BuildProtectedResourceSignature(method, parameters);
oauthParameters.Parameters.AddRange(requestParameters);
diff --git a/test/RestSharp.Tests/Auth/OAuth1Tests.cs b/test/RestSharp.Tests/Auth/OAuth1Tests.cs
index 483a89771..8f4356cf6 100644
--- a/test/RestSharp.Tests/Auth/OAuth1Tests.cs
+++ b/test/RestSharp.Tests/Auth/OAuth1Tests.cs
@@ -46,7 +46,7 @@ public async Task Can_Authenticate_OAuth1_With_Querystring_Parameters() {
authenticator.ParameterHandling = OAuthParameterHandling.UrlOrPostParameters;
await authenticator.Authenticate(client, request);
- var requestUri = client.BuildUri(request);
+ var requestUri = new Uri(client.BuildUriString(request));
var actual = requestUri.ParseQuery().Select(x => x.Key).ToList();
actual.Should().BeEquivalentTo(expected);
diff --git a/test/RestSharp.Tests/Parameters/UrlSegmentTests.cs b/test/RestSharp.Tests/Parameters/UrlSegmentTests.cs
index 5282dc57c..d7358a333 100644
--- a/test/RestSharp.Tests/Parameters/UrlSegmentTests.cs
+++ b/test/RestSharp.Tests/Parameters/UrlSegmentTests.cs
@@ -1,7 +1,8 @@
namespace RestSharp.Tests.Parameters;
public class UrlSegmentTests {
- const string BaseUrl = "http://localhost:8888/";
+ const string BaseUrlNoTrail = "http://localhost:8888";
+ const string BaseUrl = $"{BaseUrlNoTrail}/";
[Fact]
public void AddUrlSegmentWithInt() {
@@ -22,10 +23,10 @@ public void AddUrlSegmentModifiesUrlSegmentWithInt() {
var path = string.Format(pathTemplate, $"{{{name}}}");
var request = new RestRequest(path).AddUrlSegment(name, urlSegmentValue);
- var expected = string.Format(pathTemplate, urlSegmentValue);
+ var expected = $"{BaseUrlNoTrail}{string.Format(pathTemplate, urlSegmentValue)}";
using var client = new RestClient(BaseUrl);
- var actual = client.BuildUri(request).AbsolutePath;
+ var actual = client.BuildUriString(request);
expected.Should().BeEquivalentTo(actual);
}
@@ -38,11 +39,11 @@ public void AddUrlSegmentModifiesUrlSegmentWithString() {
var path = string.Format(pathTemplate, $"{{{name}}}");
var request = new RestRequest(path).AddUrlSegment(name, urlSegmentValue);
- var expected = string.Format(pathTemplate, urlSegmentValue);
+ var expected = $"{BaseUrlNoTrail}{string.Format(pathTemplate, urlSegmentValue)}";
using var client = new RestClient(BaseUrl);
- var actual = client.BuildUri(request).AbsolutePath;
+ var actual = client.BuildUriString(request);
expected.Should().BeEquivalentTo(actual);
}
@@ -73,14 +74,15 @@ public void UrlSegmentParameter_WithValueWithEncodedSlash_CanLeaveEncodedSlash(s
[Fact]
public void AddSameUrlSegmentTwice_ShouldReplaceFirst() {
- var client = new RestClient();
- var request = new RestRequest("https://api.example.com/orgs/{segment}/something");
+ const string host = "https://api.example.com";
+ var client = new RestClient();
+ var request = new RestRequest($"{host}/orgs/{{segment}}/something");
request.AddUrlSegment("segment", 1);
- var url1 = client.BuildUri(request);
+ var url1 = client.BuildUriString(request);
request.AddUrlSegment("segment", 2);
- var url2 = client.BuildUri(request);
-
- url1.AbsolutePath.Should().Be("/orgs/1/something");
- url2.AbsolutePath.Should().Be("/orgs/2/something");
+ var url2 = client.BuildUriString(request);
+
+ url1.Should().Be($"{host}/orgs/1/something");
+ url2.Should().Be($"{host}/orgs/2/something");
}
}
\ No newline at end of file
diff --git a/test/RestSharp.Tests/RestRequestTests.cs b/test/RestSharp.Tests/RestRequestTests.cs
index 988f91734..dc2053c09 100644
--- a/test/RestSharp.Tests/RestRequestTests.cs
+++ b/test/RestSharp.Tests/RestRequestTests.cs
@@ -26,8 +26,8 @@ public void RestRequest_Test_Already_Encoded() {
parameters.Should().BeEquivalentTo(expected, options => options.ExcludingMissingMembers());
using var client = new RestClient(baseUrl);
- var actual = client.BuildUri(request);
- actual.AbsoluteUri.Should().Be($"{baseUrl}{resource}");
+ var actual = client.BuildUriString(request);
+ actual.Should().Be($"{baseUrl}{resource}");
}
[Fact]
diff --git a/test/RestSharp.Tests/UrlBuilderTests.Get.cs b/test/RestSharp.Tests/UrlBuilderTests.Get.cs
index 85c04b35e..4a2909697 100644
--- a/test/RestSharp.Tests/UrlBuilderTests.Get.cs
+++ b/test/RestSharp.Tests/UrlBuilderTests.Get.cs
@@ -60,6 +60,18 @@ public void GET_with_empty_request_and_query_parameters_without_encoding() {
Assert.Equal(expected, output);
}
+ [Fact]
+ public void GET_with_pipe_character_in_query_parameter_without_encoding() {
+ var request = new RestRequest();
+ request.AddQueryParameter("ids", "in:001|116", false);
+ const string expected = $"{Base}/{Resource}?ids=in:001|116";
+
+ using var client = new RestClient($"{Base}/{Resource}");
+
+ var output = client.BuildUriString(request);
+ Assert.Equal(expected, output);
+ }
+
[Fact]
public void GET_with_Invalid_Url_string_throws_exception()
=> Assert.Throws(() => { _ = new RestClient("invalid url"); }