Skip to content

Commit d43ce02

Browse files
committed
Merge branch 'feature/restore-sync-functions' of https://github.com/kendallb/RestSharp into kendallb-feature/restore-sync-functions
2 parents 652a4c4 + 0b99780 commit d43ce02

File tree

4 files changed

+452
-0
lines changed

4 files changed

+452
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright © 2009-2021 John Sheehan, Andrew Young, Alexey Zimarev and RestSharp community
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// Adapted from Rebus
16+
17+
using System.Collections.Concurrent;
18+
using System.Runtime.ExceptionServices;
19+
20+
namespace RestSharp.Extensions {
21+
public static class AsyncHelpers {
22+
/// <summary>
23+
/// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations
24+
/// </summary>
25+
/// <param name="task">Callback for asynchronous task to run</param>
26+
public static void RunSync(Func<Task> task) {
27+
var currentContext = SynchronizationContext.Current;
28+
var customContext = new CustomSynchronizationContext(task);
29+
30+
try {
31+
SynchronizationContext.SetSynchronizationContext(customContext);
32+
customContext.Run();
33+
}
34+
finally {
35+
SynchronizationContext.SetSynchronizationContext(currentContext);
36+
}
37+
}
38+
39+
/// <summary>
40+
/// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations
41+
/// </summary>
42+
/// <param name="task">Callback for asynchronous task to run</param>
43+
/// <typeparam name="T">Return type for the task</typeparam>
44+
/// <returns>Return value from the task</returns>
45+
public static T RunSync<T>(Func<Task<T>> task) {
46+
T result = default!;
47+
RunSync(async () => { result = await task(); });
48+
return result;
49+
}
50+
51+
/// <summary>
52+
/// Synchronization context that can be "pumped" in order to have it execute continuations posted back to it
53+
/// </summary>
54+
class CustomSynchronizationContext : SynchronizationContext {
55+
readonly ConcurrentQueue<Tuple<SendOrPostCallback, object?>> _items = new();
56+
readonly AutoResetEvent _workItemsWaiting = new(false);
57+
readonly Func<Task> _task;
58+
ExceptionDispatchInfo? _caughtException;
59+
bool _done;
60+
61+
/// <summary>
62+
/// Constructor for the custom context
63+
/// </summary>
64+
/// <param name="task">Task to execute</param>
65+
public CustomSynchronizationContext(Func<Task> task) =>
66+
_task = task ?? throw new ArgumentNullException(nameof(task), "Please remember to pass a Task to be executed");
67+
68+
/// <summary>
69+
/// When overridden in a derived class, dispatches an asynchronous message to a synchronization context.
70+
/// </summary>
71+
/// <param name="function">Callback function</param>
72+
/// <param name="state">Callback state</param>
73+
public override void Post(SendOrPostCallback function, object? state) {
74+
_items.Enqueue(Tuple.Create(function, state));
75+
_workItemsWaiting.Set();
76+
}
77+
78+
/// <summary>
79+
/// Enqueues the function to be executed and executes all resulting continuations until it is completely done
80+
/// </summary>
81+
public void Run() {
82+
async void PostCallback(object? _) {
83+
try {
84+
await _task().ConfigureAwait(false);
85+
}
86+
catch (Exception exception) {
87+
_caughtException = ExceptionDispatchInfo.Capture(exception);
88+
throw;
89+
}
90+
finally {
91+
Post(_ => _done = true, null);
92+
}
93+
}
94+
95+
Post(PostCallback, null);
96+
97+
while (!_done) {
98+
if (_items.TryDequeue(out var task)) {
99+
task.Item1(task.Item2);
100+
if (_caughtException == null) {
101+
continue;
102+
}
103+
_caughtException.Throw();
104+
}
105+
else {
106+
_workItemsWaiting.WaitOne();
107+
}
108+
}
109+
}
110+
111+
/// <summary>
112+
/// When overridden in a derived class, dispatches a synchronous message to a synchronization context.
113+
/// </summary>
114+
/// <param name="function">Callback function</param>
115+
/// <param name="state">Callback state</param>
116+
public override void Send(SendOrPostCallback function, object? state) => throw new NotSupportedException("Cannot send to same thread");
117+
118+
/// <summary>
119+
/// When overridden in a derived class, creates a copy of the synchronization context. Not needed, so just return ourselves.
120+
/// </summary>
121+
/// <returns>Copy of the context</returns>
122+
public override SynchronizationContext CreateCopy() => this;
123+
}
124+
}
125+
}

src/RestSharp/RestClient.Async.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using RestSharp.Extensions;
16+
1517
namespace RestSharp;
1618

1719
public partial class RestClient {
20+
/// <summary>
21+
/// Executes the request synchronously, authenticating if needed
22+
/// </summary>
23+
/// <param name="request">Request to be executed</param>
24+
public RestResponse Execute(RestRequest request) => AsyncHelpers.RunSync(() => ExecuteAsync(request));
25+
1826
/// <summary>
1927
/// Executes the request asynchronously, authenticating if needed
2028
/// </summary>
@@ -85,6 +93,14 @@ async Task<InternalResponse> ExecuteInternal(RestRequest request, CancellationTo
8593

8694
record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception? Exception, CancellationToken TimeoutToken);
8795

96+
/// <summary>
97+
/// A specialized method to download files as streams.
98+
/// </summary>
99+
/// <param name="request">Pre-configured request instance.</param>
100+
/// <returns>The downloaded stream.</returns>
101+
[PublicAPI]
102+
public Stream? DownloadStream(RestRequest request) => AsyncHelpers.RunSync(() => DownloadStreamAsync(request));
103+
88104
/// <summary>
89105
/// A specialized method to download files as streams.
90106
/// </summary>

src/RestSharp/RestClientExtensions.Json.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
namespace RestSharp;
2020

2121
public static partial class RestClientExtensions {
22+
/// <summary>
23+
/// Calls the URL specified in the <code>resource</code> parameter, expecting a JSON response back. Deserializes and returns the response.
24+
/// </summary>
25+
/// <param name="client">RestClient instance</param>
26+
/// <param name="resource">Resource URL</param>
27+
/// <typeparam name="TResponse">Response object type</typeparam>
28+
/// <returns>Deserialized response object</returns>
29+
public static TResponse? GetJson<TResponse>(
30+
this RestClient client,
31+
string resource)
32+
=> AsyncHelpers.RunSync(() => client.GetJsonAsync<TResponse>(resource));
33+
2234
/// <summary>
2335
/// Calls the URL specified in the <code>resource</code> parameter, expecting a JSON response back. Deserializes and returns the response.
2436
/// </summary>
@@ -32,6 +44,29 @@ public static partial class RestClientExtensions {
3244
return client.GetAsync<TResponse>(request, cancellationToken);
3345
}
3446

47+
/// <summary>
48+
/// Calls the URL specified in the <code>resource</code> parameter, expecting a JSON response back. Deserializes and returns the response.
49+
/// </summary>
50+
/// <param name="client">RestClient instance</param>
51+
/// <param name="resource">Resource URL</param>
52+
/// <param name="parameters">Parameters to pass to the request</param>
53+
/// <typeparam name="TResponse">Response object type</typeparam>
54+
/// <returns>Deserialized response object</returns>
55+
public static TResponse? GetJson<TResponse>(
56+
this RestClient client,
57+
string resource,
58+
object parameters)
59+
=> AsyncHelpers.RunSync(() => client.GetJsonAsync<TResponse>(resource, parameters));
60+
61+
/// <summary>
62+
/// Calls the URL specified in the <code>resource</code> parameter, expecting a JSON response back. Deserializes and returns the response.
63+
/// </summary>
64+
/// <param name="client">RestClient instance</param>
65+
/// <param name="resource">Resource URL</param>
66+
/// <param name="parameters">Parameters to pass to the request</param>
67+
/// <param name="cancellationToken">Cancellation token</param>
68+
/// <typeparam name="TResponse">Response object type</typeparam>
69+
/// <returns>Deserialized response object</returns>
3570
public static Task<TResponse?> GetJsonAsync<TResponse>(
3671
this RestClient client,
3772
string resource,
@@ -49,6 +84,23 @@ public static partial class RestClientExtensions {
4984
return client.GetAsync<TResponse>(request, cancellationToken);
5085
}
5186

87+
/// <summary>
88+
/// Serializes the <code>request</code> object to JSON and makes a POST call to the resource specified in the <code>resource</code> parameter.
89+
/// Expects a JSON response back, deserializes it to <code>TResponse</code> type and returns it.
90+
/// </summary>
91+
/// <param name="client">RestClient instance</param>
92+
/// <param name="resource">Resource URL</param>
93+
/// <param name="request">Request object, must be serializable to JSON</param>
94+
/// <typeparam name="TRequest">Request object type</typeparam>
95+
/// <typeparam name="TResponse">Response object type</typeparam>
96+
/// <returns>Deserialized response object</returns>
97+
public static TResponse? PostJson<TRequest, TResponse>(
98+
this RestClient client,
99+
string resource,
100+
TRequest request
101+
) where TRequest : class
102+
=> AsyncHelpers.RunSync(() => client.PostJsonAsync<TRequest, TResponse>(resource, request));
103+
52104
/// <summary>
53105
/// Serializes the <code>request</code> object to JSON and makes a POST call to the resource specified in the <code>resource</code> parameter.
54106
/// Expects a JSON response back, deserializes it to <code>TResponse</code> type and returns it.
@@ -70,6 +122,22 @@ public static partial class RestClientExtensions {
70122
return client.PostAsync<TResponse>(restRequest, cancellationToken);
71123
}
72124

125+
/// <summary>
126+
/// Serializes the <code>request</code> object to JSON and makes a POST call to the resource specified in the <code>resource</code> parameter.
127+
/// Expects no response back, just the status code.
128+
/// </summary>
129+
/// <param name="client">RestClient instance</param>
130+
/// <param name="resource">Resource URL</param>
131+
/// <param name="request">Request object, must be serializable to JSON</param>
132+
/// <typeparam name="TRequest">Request object type</typeparam>
133+
/// <returns>Response status code</returns>
134+
public static HttpStatusCode PostJson<TRequest>(
135+
this RestClient client,
136+
string resource,
137+
TRequest request
138+
) where TRequest : class
139+
=> AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request));
140+
73141
/// <summary>
74142
/// Serializes the <code>request</code> object to JSON and makes a POST call to the resource specified in the <code>resource</code> parameter.
75143
/// Expects no response back, just the status code.
@@ -91,6 +159,23 @@ public static async Task<HttpStatusCode> PostJsonAsync<TRequest>(
91159
return response.StatusCode;
92160
}
93161

162+
/// <summary>
163+
/// Serializes the <code>request</code> object to JSON and makes a PUT call to the resource specified in the <code>resource</code> parameter.
164+
/// Expects a JSON response back, deserializes it to <code>TResponse</code> type and returns it.
165+
/// </summary>
166+
/// <param name="client">RestClient instance</param>
167+
/// <param name="resource">Resource URL</param>
168+
/// <param name="request">Request object, must be serializable to JSON</param>
169+
/// <typeparam name="TRequest">Request object type</typeparam>
170+
/// <typeparam name="TResponse">Response object type</typeparam>
171+
/// <returns>Deserialized response object</returns>
172+
public static TResponse? PutJson<TRequest, TResponse>(
173+
this RestClient client,
174+
string resource,
175+
TRequest request
176+
) where TRequest : class
177+
=> AsyncHelpers.RunSync(() => client.PutJsonAsync<TRequest, TResponse>(resource, request));
178+
94179
/// <summary>
95180
/// Serializes the <code>request</code> object to JSON and makes a PUT call to the resource specified in the <code>resource</code> parameter.
96181
/// Expects a JSON response back, deserializes it to <code>TResponse</code> type and returns it.
@@ -112,6 +197,22 @@ public static async Task<HttpStatusCode> PostJsonAsync<TRequest>(
112197
return client.PutAsync<TResponse>(restRequest, cancellationToken);
113198
}
114199

200+
/// <summary>
201+
/// Serializes the <code>request</code> object to JSON and makes a PUT call to the resource specified in the <code>resource</code> parameter.
202+
/// Expects no response back, just the status code.
203+
/// </summary>
204+
/// <param name="client">RestClient instance</param>
205+
/// <param name="resource">Resource URL</param>
206+
/// <param name="request">Request object, must be serializable to JSON</param>
207+
/// <typeparam name="TRequest">Request object type</typeparam>
208+
/// <returns>Response status code</returns>
209+
public static HttpStatusCode PutJson<TRequest>(
210+
this RestClient client,
211+
string resource,
212+
TRequest request
213+
) where TRequest : class
214+
=> AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request));
215+
115216
/// <summary>
116217
/// Serializes the <code>request</code> object to JSON and makes a PUT call to the resource specified in the <code>resource</code> parameter.
117218
/// Expects no response back, just the status code.

0 commit comments

Comments
 (0)