diff --git a/src/RestSharp/Extensions/AsyncHelpers.cs b/src/RestSharp/Extensions/AsyncHelpers.cs
new file mode 100644
index 000000000..1b90277f0
--- /dev/null
+++ b/src/RestSharp/Extensions/AsyncHelpers.cs
@@ -0,0 +1,125 @@
+// Copyright © 2009-2021 John Sheehan, Andrew Young, Alexey Zimarev and RestSharp community
+//
+// 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.
+//
+// Adapted from Rebus
+
+using System.Collections.Concurrent;
+using System.Runtime.ExceptionServices;
+
+namespace RestSharp.Extensions {
+ public static class AsyncHelpers {
+ ///
+ /// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations
+ ///
+ /// Callback for asynchronous task to run
+ public static void RunSync(Func task) {
+ var currentContext = SynchronizationContext.Current;
+ var customContext = new CustomSynchronizationContext(task);
+
+ try {
+ SynchronizationContext.SetSynchronizationContext(customContext);
+ customContext.Run();
+ }
+ finally {
+ SynchronizationContext.SetSynchronizationContext(currentContext);
+ }
+ }
+
+ ///
+ /// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations
+ ///
+ /// Callback for asynchronous task to run
+ /// Return type for the task
+ /// Return value from the task
+ public static T RunSync(Func> task) {
+ T result = default!;
+ RunSync(async () => { result = await task(); });
+ return result;
+ }
+
+ ///
+ /// Synchronization context that can be "pumped" in order to have it execute continuations posted back to it
+ ///
+ class CustomSynchronizationContext : SynchronizationContext {
+ readonly ConcurrentQueue> _items = new();
+ readonly AutoResetEvent _workItemsWaiting = new(false);
+ readonly Func _task;
+ ExceptionDispatchInfo? _caughtException;
+ bool _done;
+
+ ///
+ /// Constructor for the custom context
+ ///
+ /// Task to execute
+ public CustomSynchronizationContext(Func task) =>
+ _task = task ?? throw new ArgumentNullException(nameof(task), "Please remember to pass a Task to be executed");
+
+ ///
+ /// When overridden in a derived class, dispatches an asynchronous message to a synchronization context.
+ ///
+ /// Callback function
+ /// Callback state
+ public override void Post(SendOrPostCallback function, object? state) {
+ _items.Enqueue(Tuple.Create(function, state));
+ _workItemsWaiting.Set();
+ }
+
+ ///
+ /// Enqueues the function to be executed and executes all resulting continuations until it is completely done
+ ///
+ public void Run() {
+ async void PostCallback(object? _) {
+ try {
+ await _task().ConfigureAwait(false);
+ }
+ catch (Exception exception) {
+ _caughtException = ExceptionDispatchInfo.Capture(exception);
+ throw;
+ }
+ finally {
+ Post(_ => _done = true, null);
+ }
+ }
+
+ Post(PostCallback, null);
+
+ while (!_done) {
+ if (_items.TryDequeue(out var task)) {
+ task.Item1(task.Item2);
+ if (_caughtException == null) {
+ continue;
+ }
+ _caughtException.Throw();
+ }
+ else {
+ _workItemsWaiting.WaitOne();
+ }
+ }
+ }
+
+ ///
+ /// When overridden in a derived class, dispatches a synchronous message to a synchronization context.
+ ///
+ /// Callback function
+ /// Callback state
+ public override void Send(SendOrPostCallback function, object? state) => throw new NotSupportedException("Cannot send to same thread");
+
+ ///
+ /// When overridden in a derived class, creates a copy of the synchronization context. Not needed, so just return ourselves.
+ ///
+ /// Copy of the context
+ public override SynchronizationContext CreateCopy() => this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs
index 3b162c42e..5b469b958 100644
--- a/src/RestSharp/RestClient.Async.cs
+++ b/src/RestSharp/RestClient.Async.cs
@@ -12,9 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using RestSharp.Extensions;
+
namespace RestSharp;
public partial class RestClient {
+ ///
+ /// Executes the request synchronously, authenticating if needed
+ ///
+ /// Request to be executed
+ public RestResponse Execute(RestRequest request) => AsyncHelpers.RunSync(() => ExecuteAsync(request));
+
///
/// Executes the request asynchronously, authenticating if needed
///
@@ -85,6 +93,14 @@ async Task ExecuteInternal(RestRequest request, CancellationTo
record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception? Exception, CancellationToken TimeoutToken);
+ ///
+ /// A specialized method to download files as streams.
+ ///
+ /// Pre-configured request instance.
+ /// The downloaded stream.
+ [PublicAPI]
+ public Stream? DownloadStream(RestRequest request) => AsyncHelpers.RunSync(() => DownloadStreamAsync(request));
+
///
/// A specialized method to download files as streams.
///
diff --git a/src/RestSharp/RestClientExtensions.Json.cs b/src/RestSharp/RestClientExtensions.Json.cs
index 6dd7b8dee..231dac19c 100644
--- a/src/RestSharp/RestClientExtensions.Json.cs
+++ b/src/RestSharp/RestClientExtensions.Json.cs
@@ -19,6 +19,18 @@
namespace RestSharp;
public static partial class RestClientExtensions {
+ ///
+ /// Calls the URL specified in the resource
parameter, expecting a JSON response back. Deserializes and returns the response.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Response object type
+ /// Deserialized response object
+ public static TResponse? GetJson(
+ this RestClient client,
+ string resource)
+ => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource));
+
///
/// Calls the URL specified in the resource
parameter, expecting a JSON response back. Deserializes and returns the response.
///
@@ -32,6 +44,29 @@ public static partial class RestClientExtensions {
return client.GetAsync(request, cancellationToken);
}
+ ///
+ /// Calls the URL specified in the resource
parameter, expecting a JSON response back. Deserializes and returns the response.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Parameters to pass to the request
+ /// Response object type
+ /// Deserialized response object
+ public static TResponse? GetJson(
+ this RestClient client,
+ string resource,
+ object parameters)
+ => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource, parameters));
+
+ ///
+ /// Calls the URL specified in the resource
parameter, expecting a JSON response back. Deserializes and returns the response.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Parameters to pass to the request
+ /// Cancellation token
+ /// Response object type
+ /// Deserialized response object
public static Task GetJsonAsync(
this RestClient client,
string resource,
@@ -49,6 +84,23 @@ public static partial class RestClientExtensions {
return client.GetAsync(request, cancellationToken);
}
+ ///
+ /// Serializes the request
object to JSON and makes a POST call to the resource specified in the resource
parameter.
+ /// Expects a JSON response back, deserializes it to TResponse
type and returns it.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Request object, must be serializable to JSON
+ /// Request object type
+ /// Response object type
+ /// Deserialized response object
+ public static TResponse? PostJson(
+ this RestClient client,
+ string resource,
+ TRequest request
+ ) where TRequest : class
+ => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request));
+
///
/// Serializes the request
object to JSON and makes a POST call to the resource specified in the resource
parameter.
/// Expects a JSON response back, deserializes it to TResponse
type and returns it.
@@ -70,6 +122,22 @@ public static partial class RestClientExtensions {
return client.PostAsync(restRequest, cancellationToken);
}
+ ///
+ /// Serializes the request
object to JSON and makes a POST call to the resource specified in the resource
parameter.
+ /// Expects no response back, just the status code.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Request object, must be serializable to JSON
+ /// Request object type
+ /// Response status code
+ public static HttpStatusCode PostJson(
+ this RestClient client,
+ string resource,
+ TRequest request
+ ) where TRequest : class
+ => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request));
+
///
/// Serializes the request
object to JSON and makes a POST call to the resource specified in the resource
parameter.
/// Expects no response back, just the status code.
@@ -91,6 +159,23 @@ public static async Task PostJsonAsync(
return response.StatusCode;
}
+ ///
+ /// Serializes the request
object to JSON and makes a PUT call to the resource specified in the resource
parameter.
+ /// Expects a JSON response back, deserializes it to TResponse
type and returns it.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Request object, must be serializable to JSON
+ /// Request object type
+ /// Response object type
+ /// Deserialized response object
+ public static TResponse? PutJson(
+ this RestClient client,
+ string resource,
+ TRequest request
+ ) where TRequest : class
+ => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request));
+
///
/// Serializes the request
object to JSON and makes a PUT call to the resource specified in the resource
parameter.
/// Expects a JSON response back, deserializes it to TResponse
type and returns it.
@@ -112,6 +197,22 @@ public static async Task PostJsonAsync(
return client.PutAsync(restRequest, cancellationToken);
}
+ ///
+ /// Serializes the request
object to JSON and makes a PUT call to the resource specified in the resource
parameter.
+ /// Expects no response back, just the status code.
+ ///
+ /// RestClient instance
+ /// Resource URL
+ /// Request object, must be serializable to JSON
+ /// Request object type
+ /// Response status code
+ public static HttpStatusCode PutJson(
+ this RestClient client,
+ string resource,
+ TRequest request
+ ) where TRequest : class
+ => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request));
+
///
/// Serializes the request
object to JSON and makes a PUT call to the resource specified in the resource
parameter.
/// Expects no response back, just the status code.
diff --git a/src/RestSharp/RestClientExtensions.cs b/src/RestSharp/RestClientExtensions.cs
index db38adc08..27410fb35 100644
--- a/src/RestSharp/RestClientExtensions.cs
+++ b/src/RestSharp/RestClientExtensions.cs
@@ -19,6 +19,17 @@ namespace RestSharp;
[PublicAPI]
public static partial class RestClientExtensions {
+ ///
+ /// Executes a GET-style request synchronously, authenticating if needed.
+ /// The response content then gets deserialized to T.
+ ///
+ /// Target deserialization type
+ ///
+ /// Request to be executed
+ /// Deserialized response content
+ public static RestResponse ExecuteGet(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Get));
+
///
/// Executes a GET-style request asynchronously, authenticating if needed.
/// The response content then gets deserialized to T.
@@ -31,6 +42,14 @@ public static partial class RestClientExtensions {
public static Task> ExecuteGetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Get, cancellationToken);
+ ///
+ /// Executes a GET-style synchronously, authenticating if needed
+ ///
+ ///
+ /// Request to be executed
+ public static RestResponse ExecuteGet(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Get));
+
///
/// Executes a GET-style asynchronously, authenticating if needed
///
@@ -40,6 +59,20 @@ public static Task> ExecuteGetAsync(this RestClient client, R
public static Task ExecuteGetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Get, cancellationToken);
+ ///
+ /// Executes a POST-style request synchronously, authenticating if needed.
+ /// The response content then gets deserialized to T.
+ ///
+ /// Target deserialization type
+ ///
+ /// Request to be executed
+ /// Deserialized response content
+ public static RestResponse ExecutePost(
+ this RestClient client,
+ RestRequest request
+ )
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Post));
+
///
/// Executes a POST-style request asynchronously, authenticating if needed.
/// The response content then gets deserialized to T.
@@ -56,6 +89,14 @@ public static Task> ExecutePostAsync(
)
=> client.ExecuteAsync(request, Method.Post, cancellationToken);
+ ///
+ /// Executes a POST-style synchronously, authenticating if needed
+ ///
+ ///
+ /// Request to be executed
+ public static RestResponse ExecutePost(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Post));
+
///
/// Executes a POST-style asynchronously, authenticating if needed
///
@@ -65,6 +106,20 @@ public static Task> ExecutePostAsync(
public static Task ExecutePostAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Post, cancellationToken);
+ ///
+ /// Executes a PUT-style request synchronously, authenticating if needed.
+ /// The response content then gets deserialized to T.
+ ///
+ /// Target deserialization type
+ ///
+ /// Request to be executed
+ /// Deserialized response content
+ public static RestResponse ExecutePut(
+ this RestClient client,
+ RestRequest request
+ )
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Put));
+
///
/// Executes a PUT-style request asynchronously, authenticating if needed.
/// The response content then gets deserialized to T.
@@ -81,6 +136,14 @@ public static Task> ExecutePutAsync(
)
=> client.ExecuteAsync(request, Method.Put, cancellationToken);
+ ///
+ /// Executes a PUP-style synchronously, authenticating if needed
+ ///
+ ///
+ /// Request to be executed
+ public static RestResponse ExecutePut(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Put));
+
///
/// Executes a PUP-style asynchronously, authenticating if needed
///
@@ -90,6 +153,18 @@ public static Task> ExecutePutAsync(
public static Task ExecutePutAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Put, cancellationToken);
+ ///
+ /// Executes the request synchronously, authenticating if needed
+ ///
+ /// Target deserialization type
+ ///
+ /// Request to be executed
+ public static RestResponse Execute(
+ this RestClient client,
+ RestRequest request
+ )
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request));
+
///
/// Executes the request asynchronously, authenticating if needed
///
@@ -109,6 +184,19 @@ public static async Task> ExecuteAsync(
return client.Deserialize(request, response);
}
+ ///
+ /// Executes the request synchronously, authenticating if needed
+ ///
+ ///
+ /// Request to be executed
+ /// Override the request method
+ public static RestResponse Execute(
+ this RestClient client,
+ RestRequest request,
+ Method httpMethod
+ )
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, httpMethod));
+
///
/// Executes the request asynchronously, authenticating if needed
///
@@ -128,6 +216,20 @@ public static Task ExecuteAsync(
return client.ExecuteAsync(request, cancellationToken);
}
+ ///
+ /// Executes the request synchronously, authenticating if needed
+ ///
+ /// Target deserialization type
+ ///
+ /// Request to be executed
+ /// Override the request method
+ public static RestResponse Execute(
+ this RestClient client,
+ RestRequest request,
+ Method httpMethod
+ )
+ => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, httpMethod));
+
///
/// Executes the request asynchronously, authenticating if needed
///
@@ -148,6 +250,17 @@ public static Task> ExecuteAsync(
return client.ExecuteAsync(request, cancellationToken);
}
+ ///
+ /// Execute the request using GET HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Get(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.GetAsync(request));
+
///
/// Execute the request using GET HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -163,12 +276,26 @@ public static Task> ExecuteAsync(
return response.Data;
}
+ public static RestResponse Get(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.GetAsync(request));
+
public static async Task GetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecuteGetAsync(request, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// Execute the request using POST HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Post(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.PostAsync(request));
+
///
/// Execute the request using POST HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -184,12 +311,26 @@ public static async Task GetAsync(this RestClient client, RestRequ
return response.Data;
}
+ public static RestResponse Post(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.PostAsync(request));
+
public static async Task PostAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecutePostAsync(request, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// Execute the request using PUT HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Put(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.PutAsync(request));
+
///
/// Execute the request using PUT HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -205,12 +346,26 @@ public static async Task PostAsync(this RestClient client, RestReq
return response.Data;
}
+ public static RestResponse Put(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.PutAsync(request));
+
public static async Task PutAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecuteAsync(request, Method.Put, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// Execute the request using HEAD HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Head(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.HeadAsync(request));
+
///
/// Execute the request using HEAD HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -226,12 +381,26 @@ public static async Task PutAsync(this RestClient client, RestRequ
return response.Data;
}
+ public static RestResponse Head(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.HeadAsync(request));
+
public static async Task HeadAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecuteAsync(request, Method.Head, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// Execute the request using OPTIONS HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Options(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.OptionsAsync(request));
+
///
/// Execute the request using OPTIONS HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -247,12 +416,26 @@ public static async Task HeadAsync(this RestClient client, RestReq
return response.Data;
}
+ public static RestResponse Options(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.OptionsAsync(request));
+
public static async Task OptionsAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecuteAsync(request, Method.Options, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// Execute the request using PATCH HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Patch(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.PatchAsync(request));
+
///
/// Execute the request using PATCH HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -268,12 +451,26 @@ public static async Task OptionsAsync(this RestClient client, Rest
return response.Data;
}
+ public static RestResponse Patch(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.PatchAsync(request));
+
public static async Task PatchAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecuteAsync(request, Method.Patch, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// Execute the request using DELETE HTTP method. Exception will be thrown if the request does not succeed.
+ /// The response data is deserialized to the Data property of the returned response object.
+ ///
+ /// RestClient instance
+ /// The request
+ /// Expected result type
+ ///
+ public static T? Delete(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.DeleteAsync(request));
+
///
/// Execute the request using DELETE HTTP method. Exception will be thrown if the request does not succeed.
/// The response data is deserialized to the Data property of the returned response object.
@@ -289,12 +486,25 @@ public static async Task PatchAsync(this RestClient client, RestRe
return response.Data;
}
+ public static RestResponse Delete(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.DeleteAsync(request));
+
public static async Task DeleteAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
var response = await client.ExecuteAsync(request, Method.Delete, cancellationToken).ConfigureAwait(false);
RestClient.ThrowIfError(response);
return response;
}
+ ///
+ /// A specialized method to download files.
+ ///
+ /// RestClient instance
+ /// Pre-configured request instance.
+ /// The downloaded file.
+ [PublicAPI]
+ public static byte[]? DownloadData(this RestClient client, RestRequest request)
+ => AsyncHelpers.RunSync(() => client.DownloadDataAsync(request));
+
///
/// A specialized method to download files.
///