Skip to content

Commit 99f40bb

Browse files
committed
Merge branch 'kendallb-feature/restore-sync-functions' into dev
2 parents 652a4c4 + 79fdb32 commit 99f40bb

13 files changed

+515
-9
lines changed

src/RestSharp/Request/RequestContent.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
using RestSharp.Extensions;
1818
using static RestSharp.KnownHeaders;
1919
// ReSharper disable InvertIf
20-
2120
// ReSharper disable SuggestBaseTypeForParameter
2221

2322
namespace RestSharp;

src/RestSharp/RestClient.Async.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
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 {

src/RestSharp/RestClientExtensions.Json.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public static partial class RestClientExtensions {
3232
return client.GetAsync<TResponse>(request, cancellationToken);
3333
}
3434

35+
/// <summary>
36+
/// Calls the URL specified in the <code>resource</code> parameter, expecting a JSON response back. Deserializes and returns the response.
37+
/// </summary>
38+
/// <param name="client">RestClient instance</param>
39+
/// <param name="resource">Resource URL</param>
40+
/// <param name="parameters">Parameters to pass to the request</param>
41+
/// <param name="cancellationToken">Cancellation token</param>
42+
/// <typeparam name="TResponse">Response object type</typeparam>
43+
/// <returns>Deserialized response object</returns>
3544
public static Task<TResponse?> GetJsonAsync<TResponse>(
3645
this RestClient client,
3746
string resource,

src/RestSharp/RestClientExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public static Task<RestResponse<T>> ExecuteAsync<T>(
163163
RestClient.ThrowIfError(response);
164164
return response.Data;
165165
}
166-
166+
167167
public static async Task<RestResponse> GetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
168168
var response = await client.ExecuteGetAsync(request, cancellationToken).ConfigureAwait(false);
169169
RestClient.ThrowIfError(response);
@@ -185,6 +185,9 @@ public static async Task<RestResponse> GetAsync(this RestClient client, RestRequ
185185
return response.Data;
186186
}
187187

188+
public static RestResponse Post(this RestClient client, RestRequest request)
189+
=> AsyncHelpers.RunSync(() => client.PostAsync(request));
190+
188191
public static async Task<RestResponse> PostAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
189192
var response = await client.ExecutePostAsync(request, cancellationToken).ConfigureAwait(false);
190193
RestClient.ThrowIfError(response);

src/RestSharp/RestSharp.csproj.DotSettings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=http/@EntryIndexedValue">True</s:Boolean>
33
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=parameters/@EntryIndexedValue">True</s:Boolean>
44
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=request/@EntryIndexedValue">True</s:Boolean>
5-
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=response/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
5+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=response/@EntryIndexedValue">True</s:Boolean>
6+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=sync/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/RestSharp/Sync/AsyncHelpers.cs

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 {
21+
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+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright © 2009-2020 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+
16+
namespace RestSharp;
17+
18+
public partial class RestClient {
19+
/// <summary>
20+
/// Executes the request synchronously, authenticating if needed
21+
/// </summary>
22+
/// <param name="request">Request to be executed</param>
23+
public RestResponse Execute(RestRequest request) => AsyncHelpers.RunSync(() => ExecuteAsync(request));
24+
25+
/// <summary>
26+
/// A specialized method to download files as streams.
27+
/// </summary>
28+
/// <param name="request">Pre-configured request instance.</param>
29+
/// <returns>The downloaded stream.</returns>
30+
[PublicAPI]
31+
public Stream? DownloadStream(RestRequest request) => AsyncHelpers.RunSync(() => DownloadStreamAsync(request));
32+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
16+
using System.Net;
17+
using RestSharp.Extensions;
18+
19+
namespace RestSharp;
20+
21+
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+
34+
/// <summary>
35+
/// Calls the URL specified in the <code>resource</code> parameter, expecting a JSON response back. Deserializes and returns the response.
36+
/// </summary>
37+
/// <param name="client">RestClient instance</param>
38+
/// <param name="resource">Resource URL</param>
39+
/// <param name="parameters">Parameters to pass to the request</param>
40+
/// <typeparam name="TResponse">Response object type</typeparam>
41+
/// <returns>Deserialized response object</returns>
42+
public static TResponse? GetJson<TResponse>(
43+
this RestClient client,
44+
string resource,
45+
object parameters)
46+
=> AsyncHelpers.RunSync(() => client.GetJsonAsync<TResponse>(resource, parameters));
47+
48+
/// <summary>
49+
/// Serializes the <code>request</code> object to JSON and makes a POST call to the resource specified in the <code>resource</code> parameter.
50+
/// Expects a JSON response back, deserializes it to <code>TResponse</code> type and returns it.
51+
/// </summary>
52+
/// <param name="client">RestClient instance</param>
53+
/// <param name="resource">Resource URL</param>
54+
/// <param name="request">Request object, must be serializable to JSON</param>
55+
/// <typeparam name="TRequest">Request object type</typeparam>
56+
/// <typeparam name="TResponse">Response object type</typeparam>
57+
/// <returns>Deserialized response object</returns>
58+
public static TResponse? PostJson<TRequest, TResponse>(
59+
this RestClient client,
60+
string resource,
61+
TRequest request
62+
) where TRequest : class
63+
=> AsyncHelpers.RunSync(() => client.PostJsonAsync<TRequest, TResponse>(resource, request));
64+
65+
/// <summary>
66+
/// Serializes the <code>request</code> object to JSON and makes a POST call to the resource specified in the <code>resource</code> parameter.
67+
/// Expects no response back, just the status code.
68+
/// </summary>
69+
/// <param name="client">RestClient instance</param>
70+
/// <param name="resource">Resource URL</param>
71+
/// <param name="request">Request object, must be serializable to JSON</param>
72+
/// <typeparam name="TRequest">Request object type</typeparam>
73+
/// <returns>Response status code</returns>
74+
public static HttpStatusCode PostJson<TRequest>(
75+
this RestClient client,
76+
string resource,
77+
TRequest request
78+
) where TRequest : class
79+
=> AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request));
80+
81+
/// <summary>
82+
/// Serializes the <code>request</code> object to JSON and makes a PUT call to the resource specified in the <code>resource</code> parameter.
83+
/// Expects a JSON response back, deserializes it to <code>TResponse</code> type and returns it.
84+
/// </summary>
85+
/// <param name="client">RestClient instance</param>
86+
/// <param name="resource">Resource URL</param>
87+
/// <param name="request">Request object, must be serializable to JSON</param>
88+
/// <typeparam name="TRequest">Request object type</typeparam>
89+
/// <typeparam name="TResponse">Response object type</typeparam>
90+
/// <returns>Deserialized response object</returns>
91+
public static TResponse? PutJson<TRequest, TResponse>(
92+
this RestClient client,
93+
string resource,
94+
TRequest request
95+
) where TRequest : class
96+
=> AsyncHelpers.RunSync(() => client.PutJsonAsync<TRequest, TResponse>(resource, request));
97+
98+
/// <summary>
99+
/// Serializes the <code>request</code> object to JSON and makes a PUT call to the resource specified in the <code>resource</code> parameter.
100+
/// Expects no response back, just the status code.
101+
/// </summary>
102+
/// <param name="client">RestClient instance</param>
103+
/// <param name="resource">Resource URL</param>
104+
/// <param name="request">Request object, must be serializable to JSON</param>
105+
/// <typeparam name="TRequest">Request object type</typeparam>
106+
/// <returns>Response status code</returns>
107+
public static HttpStatusCode PutJson<TRequest>(
108+
this RestClient client,
109+
string resource,
110+
TRequest request
111+
) where TRequest : class
112+
=> AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request));
113+
}

0 commit comments

Comments
 (0)