From 0b99780a3820c0f6be8657a0823b64ff0380cde5 Mon Sep 17 00:00:00 2001 From: Kendall Bennett Date: Thu, 17 Mar 2022 19:12:48 -0400 Subject: [PATCH] Added back all missing Async API functions (except for json streaming via async not sure how to convert that one). --- src/RestSharp/Extensions/AsyncHelpers.cs | 125 ++++++++++++ src/RestSharp/RestClient.Async.cs | 16 ++ src/RestSharp/RestClientExtensions.Json.cs | 101 ++++++++++ src/RestSharp/RestClientExtensions.cs | 210 +++++++++++++++++++++ 4 files changed, 452 insertions(+) create mode 100644 src/RestSharp/Extensions/AsyncHelpers.cs 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. ///