From ba993f881db0973266c25a2d6dce1a52dc8e6747 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 4 Apr 2023 17:53:52 +0200 Subject: [PATCH 1/4] Added remaining overloads (PATCH, HEAD, OPTIONS, DELETE) Fixed sync extensions to use IRestClient --- RestSharp.sln.DotSettings | 1 + src/RestSharp/AsyncHelpers.cs | 125 +++++++++ .../CookieContainerExtensions.cs} | 33 ++- src/RestSharp/RestClient.Async.cs | 14 +- src/RestSharp/RestClient.Extensions.Config.cs | 38 --- src/RestSharp/RestClient.Extensions.Delete.cs | 106 ++++++++ src/RestSharp/RestClient.Extensions.Get.cs | 168 ++++++++++++ src/RestSharp/RestClient.Extensions.Head.cs | 106 ++++++++ src/RestSharp/RestClient.Extensions.Json.cs | 124 --------- .../RestClient.Extensions.Options.cs | 106 ++++++++ src/RestSharp/RestClient.Extensions.Patch.cs | 108 ++++++++ src/RestSharp/RestClient.Extensions.Post.cs | 175 ++++++++++++ src/RestSharp/RestClient.Extensions.Put.cs | 175 ++++++++++++ src/RestSharp/RestClient.Extensions.cs | 255 ++++-------------- src/RestSharp/RestSharp.csproj | 24 +- src/RestSharp/Sync/AsyncHelpers.cs | 125 --------- src/RestSharp/Sync/RestClient.Sync.cs | 38 --- .../Sync/RestClientExtensions.Sync.Json.cs | 112 -------- .../Sync/RestClientExtensions.Sync.cs | 227 ---------------- 19 files changed, 1163 insertions(+), 897 deletions(-) create mode 100644 src/RestSharp/AsyncHelpers.cs rename src/RestSharp/{RestClient.Serialization.cs => Extensions/CookieContainerExtensions.cs} (50%) delete mode 100644 src/RestSharp/RestClient.Extensions.Config.cs create mode 100644 src/RestSharp/RestClient.Extensions.Delete.cs create mode 100644 src/RestSharp/RestClient.Extensions.Get.cs create mode 100644 src/RestSharp/RestClient.Extensions.Head.cs create mode 100644 src/RestSharp/RestClient.Extensions.Options.cs create mode 100644 src/RestSharp/RestClient.Extensions.Patch.cs create mode 100644 src/RestSharp/RestClient.Extensions.Post.cs create mode 100644 src/RestSharp/RestClient.Extensions.Put.cs delete mode 100644 src/RestSharp/Sync/AsyncHelpers.cs delete mode 100644 src/RestSharp/Sync/RestClient.Sync.cs delete mode 100644 src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs delete mode 100644 src/RestSharp/Sync/RestClientExtensions.Sync.cs diff --git a/RestSharp.sln.DotSettings b/RestSharp.sln.DotSettings index 764b7bbf2..9afb8ff44 100644 --- a/RestSharp.sln.DotSettings +++ b/RestSharp.sln.DotSettings @@ -80,6 +80,7 @@ True True True + True True True True diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs new file mode 100644 index 000000000..d65bac248 --- /dev/null +++ b/src/RestSharp/AsyncHelpers.cs @@ -0,0 +1,125 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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; + +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.Serialization.cs b/src/RestSharp/Extensions/CookieContainerExtensions.cs similarity index 50% rename from src/RestSharp/RestClient.Serialization.cs rename to src/RestSharp/Extensions/CookieContainerExtensions.cs index d36954966..519a497d3 100644 --- a/src/RestSharp/RestClient.Serialization.cs +++ b/src/RestSharp/Extensions/CookieContainerExtensions.cs @@ -1,26 +1,31 @@ -// Copyright (c) .NET Foundation and Contributors -// +// Copyright (c) .NET Foundation and Contributors +// // 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. +// -using RestSharp.Extensions; -using RestSharp.Serializers; -using RestSharp.Serializers.Json; -using RestSharp.Serializers.Xml; +using System.Net; -// ReSharper disable VirtualMemberCallInConstructor -#pragma warning disable 618 +namespace RestSharp.Extensions; -namespace RestSharp; - -public partial class RestClient { -} \ No newline at end of file +static class CookieContainerExtensions { + public static void AddCookies(this CookieContainer cookieContainer, Uri uri, IEnumerable cookiesHeader) { + foreach (var header in cookiesHeader) { + try { + cookieContainer.SetCookies(uri, header); + } + catch (CookieException) { + // Do not fail request if we cannot parse a cookie + } + } + } +} diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index 821f4f487..cde92daf1 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -89,7 +89,10 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo using var requestContent = new RequestContent(this, request); var authenticator = request.Authenticator ?? Options.Authenticator; - if (authenticator != null) await authenticator.Authenticate(this, request).ConfigureAwait(false); + + if (authenticator != null) { + await authenticator.Authenticate(this, request).ConfigureAwait(false); + } var httpMethod = AsHttpMethod(request.Method); var url = this.BuildUri(request); @@ -124,14 +127,7 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo // Parse all the cookies from the response and update the cookie jar with cookies if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) { - foreach (var header in cookiesHeader) { - try { - cookieContainer.SetCookies(url, header); - } - catch (CookieException) { - // Do not fail request if we cannot parse a cookie - } - } + cookieContainer.AddCookies(url, cookiesHeader); } if (request.OnAfterRequest != null) await request.OnAfterRequest(responseMessage).ConfigureAwait(false); diff --git a/src/RestSharp/RestClient.Extensions.Config.cs b/src/RestSharp/RestClient.Extensions.Config.cs deleted file mode 100644 index 006bdc571..000000000 --- a/src/RestSharp/RestClient.Extensions.Config.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors -// -// 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. -// - -using System.Text; -using RestSharp.Authenticators; -using RestSharp.Extensions; - -namespace RestSharp; - -public static partial class RestClientExtensions { - [PublicAPI] - public static RestResponse Deserialize(this IRestClient client, RestResponse response) - => client.Serializers.Deserialize(response.Request!, response, client.Options); - - [Obsolete("Set the RestClientOptions.Encode property instead")] - public static RestClient UseUrlEncoder(this RestClient client, Func encoder) - => throw new NotImplementedException("Set the RestClientOptions.Encode property instead"); - - [Obsolete("Set the RestClientOptions.EncodeQuery property instead")] - public static RestClient UseQueryEncoder(this RestClient client, Func queryEncoder) - => throw new NotImplementedException("Set the RestClientOptions.EncodeQuery property instead"); - - [Obsolete("Set the RestClientOptions.Authenticator property instead")] - public static RestClient UseAuthenticator(this RestClient client, IAuthenticator authenticator) - => throw new NotImplementedException("Set the RestClientOptions.Authenticator property instead"); -} diff --git a/src/RestSharp/RestClient.Extensions.Delete.cs b/src/RestSharp/RestClient.Extensions.Delete.cs new file mode 100644 index 000000000..32172e9e0 --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Delete.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a DELETE-style request asynchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecuteDeleteAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => client.ExecuteAsync(request, Method.Delete, cancellationToken); + + /// + /// Executes a DELETE-style synchronously, authenticating if needed + /// + /// + /// Request to be executed + public static RestResponse ExecuteDelete(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Delete)); + + /// + /// Executes a DELETE-style request asynchronously, authenticating if needed. + /// The response content then gets deserialized to T. + /// + /// Target deserialization type + /// + /// Request to be executed + /// The cancellation token + /// Deserialized response content + public static Task> ExecuteDeleteAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Delete, cancellationToken); + + /// + /// Executes a DELETE-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 ExecuteDelete(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Delete)); + + /// + /// 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 + /// Cancellation token + /// Expected result type + /// + public static async Task DeleteAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Delete, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task DeleteAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Delete, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using DELETE HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + public static RestResponse Delete(this IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.DeleteAsync(request)); +} diff --git a/src/RestSharp/RestClient.Extensions.Get.cs b/src/RestSharp/RestClient.Extensions.Get.cs new file mode 100644 index 000000000..5bc52dff3 --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Get.cs @@ -0,0 +1,168 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a GET-style asynchronously, authenticating if needed. + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecuteGetAsync(this IRestClient 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 IRestClient 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. + /// + /// Target deserialization type + /// + /// Request to be executed + /// Cancellation token + /// Deserialized response content + public static Task> ExecuteGetAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Get, cancellationToken); + + /// + /// 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 IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Get)); + + /// + /// Execute the request using GET HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task GetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteGetAsync(request, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using GET HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + public static RestResponse Get(this IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// Expected result type + /// + public static async Task GetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteGetAsync(request, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.GetAsync(request)); + + /// + /// Calls the URL specified in the resource parameter, expecting a JSON response back. Deserializes and returns the response. + /// + /// RestClient instance + /// Resource URL + /// Cancellation token + /// Response object type + /// + public static Task GetJsonAsync(this IRestClient client, string resource, CancellationToken cancellationToken = default) { + var request = new RestRequest(resource); + 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 + /// Response object type + /// Deserialized response object + public static TResponse? GetJson(this IRestClient 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. + /// + /// RestClient instance + /// Resource URL + /// Parameters to pass to the request + /// Cancellation token + /// Response object type + /// Deserialized response object + public static Task GetJsonAsync( + this IRestClient client, + string resource, + object parameters, + CancellationToken cancellationToken = default + ) { + var props = parameters.GetProperties(); + var request = new RestRequest(resource); + + foreach (var prop in props) { + Parameter parameter = resource.Contains($"{prop.Name}") + ? new UrlSegmentParameter(prop.Name, prop.Value!, prop.Encode) + : new QueryParameter(prop.Name, prop.Value, prop.Encode); + request.AddParameter(parameter); + } + + 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 IRestClient client, string resource, object parameters) + => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource, parameters)); +} diff --git a/src/RestSharp/RestClient.Extensions.Head.cs b/src/RestSharp/RestClient.Extensions.Head.cs new file mode 100644 index 000000000..c30402c02 --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Head.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a HEAD-style request asynchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecuteHeadAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => client.ExecuteAsync(request, Method.Head, cancellationToken); + + /// + /// Executes a HEAD-style synchronously, authenticating if needed + /// + /// + /// Request to be executed + public static RestResponse ExecuteHead(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Head)); + + /// + /// Executes a HEAD-style request asynchronously, authenticating if needed. + /// The response content then gets deserialized to T. + /// + /// Target deserialization type + /// + /// Request to be executed + /// The cancellation token + /// Deserialized response content + public static Task> ExecuteHeadAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Head, cancellationToken); + + /// + /// Executes a HEAD-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 ExecuteHead(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Head)); + + /// + /// 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 + /// Cancellation token + /// Expected result type + /// + public static async Task HeadAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Head, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task HeadAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Head, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using HEAD HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + public static RestResponse Head(this IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.HeadAsync(request)); +} diff --git a/src/RestSharp/RestClient.Extensions.Json.cs b/src/RestSharp/RestClient.Extensions.Json.cs index 3f5a3cbbd..2460169b7 100644 --- a/src/RestSharp/RestClient.Extensions.Json.cs +++ b/src/RestSharp/RestClient.Extensions.Json.cs @@ -19,128 +19,4 @@ 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 - /// Cancellation token - /// Response object type - /// - public static Task GetJsonAsync(this RestClient client, string resource, CancellationToken cancellationToken = default) { - var request = new RestRequest(resource); - 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 - /// Cancellation token - /// Response object type - /// Deserialized response object - public static Task GetJsonAsync( - this RestClient client, - string resource, - object parameters, - CancellationToken cancellationToken = default - ) { - var props = parameters.GetProperties(); - var request = new RestRequest(resource); - - foreach (var prop in props) { - Parameter parameter = resource.Contains($"{prop.Name}") - ? new UrlSegmentParameter(prop.Name, prop.Value!, prop.Encode) - : new QueryParameter(prop.Name, prop.Value, prop.Encode); - request.AddParameter(parameter); - } - - 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 - /// Cancellation token - /// Request object type - /// Response object type - /// Deserialized response object - public static Task PostJsonAsync( - this RestClient client, - string resource, - TRequest request, - CancellationToken cancellationToken = default - ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); - 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 - /// Cancellation token - /// Request object type - /// Response status code - public static async Task PostJsonAsync( - this RestClient client, - string resource, - TRequest request, - CancellationToken cancellationToken = default - ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); - var response = await client.PostAsync(restRequest, cancellationToken).ConfigureAwait(false); - 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 - /// Cancellation token - /// Request object type - /// Response object type - /// Deserialized response object - public static Task PutJsonAsync( - this RestClient client, - string resource, - TRequest request, - CancellationToken cancellationToken = default - ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); - 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 - /// Cancellation token - /// Request object type - /// Response status code - public static async Task PutJsonAsync( - this RestClient client, - string resource, - TRequest request, - CancellationToken cancellationToken = default - ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); - var response = await client.PutAsync(restRequest, cancellationToken).ConfigureAwait(false); - return response.StatusCode; - } } diff --git a/src/RestSharp/RestClient.Extensions.Options.cs b/src/RestSharp/RestClient.Extensions.Options.cs new file mode 100644 index 000000000..7163fcf51 --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Options.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a OPTIONS-style request asynchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecuteOptionsAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => client.ExecuteAsync(request, Method.Options, cancellationToken); + + /// + /// Executes a OPTIONS-style synchronously, authenticating if needed + /// + /// + /// Request to be executed + public static RestResponse ExecuteOptions(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Options)); + + /// + /// Executes a OPTIONS-style request asynchronously, authenticating if needed. + /// The response content then gets deserialized to T. + /// + /// Target deserialization type + /// + /// Request to be executed + /// The cancellation token + /// Deserialized response content + public static Task> ExecuteOptionsAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Options, cancellationToken); + + /// + /// Executes a OPTIONS-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 ExecuteOptions(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Options)); + + /// + /// Execute the request using OPTIONS HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task OptionsAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Options, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using OPTIONS HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + public static RestResponse Options(this IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// Expected result type + /// + public static async Task OptionsAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Options, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.OptionsAsync(request)); +} diff --git a/src/RestSharp/RestClient.Extensions.Patch.cs b/src/RestSharp/RestClient.Extensions.Patch.cs new file mode 100644 index 000000000..44547ab7f --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Patch.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a PUT-style request asynchronously, authenticating if needed. + /// The response content then gets deserialized to T. + /// + /// Target deserialization type + /// + /// Request to be executed + /// The cancellation token + /// Deserialized response content + public static Task> ExecutePatchAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Patch, cancellationToken); + + /// + /// Executes a PATCH-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 ExecutePatch(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Patch)); + + /// + /// Executes a PATCH-style request asynchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecutePatchAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => client.ExecuteAsync(request, Method.Patch, cancellationToken); + + /// + /// Executes a PATCH-style synchronously, authenticating if needed + /// + /// + /// Request to be executed + public static RestResponse ExecutePatch(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Patch)); + + /// + /// 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 + /// Cancellation token + /// Expected result type + /// + public static async Task PatchAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Patch, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 + /// + [PublicAPI] + public static T? Patch(this IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task PatchAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Patch, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using PATCH HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + [PublicAPI] + public static RestResponse Patch(this IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.PatchAsync(request)); +} diff --git a/src/RestSharp/RestClient.Extensions.Post.cs b/src/RestSharp/RestClient.Extensions.Post.cs new file mode 100644 index 000000000..beddd15a5 --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Post.cs @@ -0,0 +1,175 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +using System.Net; + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a POST-style request asynchronously, authenticating if needed. + /// The response content then gets deserialized to T. + /// + /// Target deserialization type + /// + /// Request to be executed + /// The cancellation token + /// Deserialized response content + public static Task> ExecutePostAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Post, 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 IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Post)); + + /// + /// Executes a POST-style asynchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecutePostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => client.ExecuteAsync(request, Method.Post, cancellationToken); + + /// + /// Executes a POST-style synchronously, authenticating if needed + /// + /// + /// Request to be executed + public static RestResponse ExecutePost(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Post)); + + /// + /// 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 + /// Cancellation token + /// Expected result type + /// + public static async Task PostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecutePostAsync(request, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task PostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecutePostAsync(request, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using POST HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + public static RestResponse Post(this IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.PostAsync(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. + /// + /// RestClient instance + /// Resource URL + /// Request object, must be serializable to JSON + /// Cancellation token + /// Request object type + /// Response object type + /// Deserialized response object + public static Task PostJsonAsync( + this IRestClient client, + string resource, + TRequest request, + CancellationToken cancellationToken = default + ) where TRequest : class { + var restRequest = new RestRequest(resource).AddJsonBody(request); + 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 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 IRestClient 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. + /// + /// RestClient instance + /// Resource URL + /// Request object, must be serializable to JSON + /// Cancellation token + /// Request object type + /// Response status code + public static async Task PostJsonAsync( + this IRestClient client, + string resource, + TRequest request, + CancellationToken cancellationToken = default + ) where TRequest : class { + var restRequest = new RestRequest(resource).AddJsonBody(request); + var response = await client.PostAsync(restRequest, cancellationToken).ConfigureAwait(false); + return response.StatusCode; + } + + /// + /// 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 IRestClient client, string resource, TRequest request) where TRequest : class + => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request)); +} diff --git a/src/RestSharp/RestClient.Extensions.Put.cs b/src/RestSharp/RestClient.Extensions.Put.cs new file mode 100644 index 000000000..44a37d768 --- /dev/null +++ b/src/RestSharp/RestClient.Extensions.Put.cs @@ -0,0 +1,175 @@ +// Copyright (c) .NET Foundation and Contributors +// +// 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. +// + +using System.Net; + +namespace RestSharp; + +public static partial class RestClientExtensions { + /// + /// Executes a PUT-style request asynchronously, authenticating if needed. + /// The response content then gets deserialized to T. + /// + /// Target deserialization type + /// + /// Request to be executed + /// The cancellation token + /// Deserialized response content + public static Task> ExecutePutAsync( + this IRestClient client, + RestRequest request, + CancellationToken cancellationToken = default + ) + => client.ExecuteAsync(request, Method.Put, 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 IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Put)); + + /// + /// Executes a PUP-style request asynchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Cancellation token + public static Task ExecutePutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => client.ExecuteAsync(request, Method.Put, cancellationToken); + + /// + /// Executes a PUT-style synchronously, authenticating if needed + /// + /// + /// Request to be executed + public static RestResponse ExecutePut(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, Method.Put)); + + /// + /// 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 + /// Cancellation token + /// Expected result type + /// Deserialaized response + public static async Task PutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Put, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError().Data; + } + + /// + /// 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 IRestClient 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. + /// + /// RestClient instance + /// The request + /// Cancellation token + /// + public static async Task PutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { + var response = await client.ExecuteAsync(request, Method.Put, cancellationToken).ConfigureAwait(false); + return response.ThrowIfError(); + } + + /// + /// Execute the request using PUT HTTP method. Exception will be thrown if the request does not succeed. + /// + /// RestClient instance + /// The request + /// + public static RestResponse Put(this IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.PutAsync(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. + /// + /// RestClient instance + /// Resource URL + /// Request object, must be serializable to JSON + /// Cancellation token + /// Request object type + /// Response object type + /// Deserialized response object + public static Task PutJsonAsync( + this IRestClient client, + string resource, + TRequest request, + CancellationToken cancellationToken = default + ) where TRequest : class { + var restRequest = new RestRequest(resource).AddJsonBody(request); + 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 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 IRestClient 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. + /// + /// RestClient instance + /// Resource URL + /// Request object, must be serializable to JSON + /// Cancellation token + /// Request object type + /// Response status code + public static async Task PutJsonAsync( + this IRestClient client, + string resource, + TRequest request, + CancellationToken cancellationToken = default + ) where TRequest : class { + var restRequest = new RestRequest(resource).AddJsonBody(request); + var response = await client.PutAsync(restRequest, cancellationToken).ConfigureAwait(false); + return response.StatusCode; + } + + /// + /// 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 IRestClient client, string resource, TRequest request) where TRequest : class + => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request)); +} diff --git a/src/RestSharp/RestClient.Extensions.cs b/src/RestSharp/RestClient.Extensions.cs index 1ed035160..56f8be3e6 100644 --- a/src/RestSharp/RestClient.Extensions.cs +++ b/src/RestSharp/RestClient.Extensions.cs @@ -14,82 +14,14 @@ using System.Runtime.CompilerServices; using RestSharp.Extensions; -using RestSharp.Serializers; namespace RestSharp; [PublicAPI] public static partial class RestClientExtensions { - /// - /// Executes a GET-style request asynchronously, authenticating if needed. - /// The response content then gets deserialized to T. - /// - /// Target deserialization type - /// - /// Request to be executed - /// Cancellation token - /// Deserialized response content - public static Task> ExecuteGetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) - => client.ExecuteAsync(request, Method.Get, cancellationToken); - - /// - /// Executes a GET-style asynchronously, authenticating if needed - /// - /// - /// Request to be executed - /// Cancellation token - public static Task ExecuteGetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) - => client.ExecuteAsync(request, Method.Get, cancellationToken); - - /// - /// Executes a POST-style request asynchronously, authenticating if needed. - /// The response content then gets deserialized to T. - /// - /// Target deserialization type - /// - /// Request to be executed - /// The cancellation token - /// Deserialized response content - public static Task> ExecutePostAsync( - this IRestClient client, - RestRequest request, - CancellationToken cancellationToken = default - ) - => client.ExecuteAsync(request, Method.Post, cancellationToken); - - /// - /// Executes a POST-style asynchronously, authenticating if needed - /// - /// - /// Request to be executed - /// Cancellation token - public static Task ExecutePostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) - => client.ExecuteAsync(request, Method.Post, cancellationToken); - - /// - /// Executes a PUT-style request asynchronously, authenticating if needed. - /// The response content then gets deserialized to T. - /// - /// Target deserialization type - /// - /// Request to be executed - /// The cancellation token - /// Deserialized response content - public static Task> ExecutePutAsync( - this IRestClient client, - RestRequest request, - CancellationToken cancellationToken = default - ) - => client.ExecuteAsync(request, Method.Put, cancellationToken); - - /// - /// Executes a PUP-style asynchronously, authenticating if needed - /// - /// - /// Request to be executed - /// Cancellation token - public static Task ExecutePutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) - => client.ExecuteAsync(request, Method.Put, cancellationToken); + [PublicAPI] + public static RestResponse Deserialize(this IRestClient client, RestResponse response) + => client.Serializers.Deserialize(response.Request, response, client.Options); /// /// Executes the request asynchronously, authenticating if needed @@ -103,13 +35,21 @@ public static async Task> ExecuteAsync( RestRequest request, CancellationToken cancellationToken = default ) { - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) throw new ArgumentNullException(nameof(request)); var response = await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); return client.Serializers.Deserialize(request, response, client.Options); } + /// + /// Executes the request synchronously, authenticating if needed + /// + /// Target deserialization type + /// + /// Request to be executed + public static RestResponse Execute(this IRestClient client, RestRequest request) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request)); + /// /// Executes the request asynchronously, authenticating if needed /// @@ -129,6 +69,15 @@ public static Task ExecuteAsync( return client.ExecuteAsync(request, cancellationToken); } + /// + /// Executes the request synchronously, authenticating if needed + /// + /// + /// Request to be executed + /// Override the request method + public static RestResponse Execute(this IRestClient client, RestRequest request, Method httpMethod) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, httpMethod)); + /// /// Executes the request asynchronously, authenticating if needed /// @@ -150,140 +99,14 @@ public static Task> ExecuteAsync( } /// - /// 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 - /// Cancellation token - /// Expected result type - /// - public static async Task GetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteGetAsync(request, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static async Task GetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteGetAsync(request, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } - - /// - /// 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 - /// Cancellation token - /// Expected result type - /// - public static async Task PostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecutePostAsync(request, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static RestResponse Post(this IRestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.PostAsync(request)); - - public static async Task PostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecutePostAsync(request, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } - - /// - /// 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 - /// Cancellation token - /// Expected result type - /// - public static async Task PutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Put, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static async Task PutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Put, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } - - /// - /// 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 - /// Cancellation token - /// Expected result type - /// - public static async Task HeadAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Head, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static async Task HeadAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Head, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } - - /// - /// 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 - /// Cancellation token - /// Expected result type - /// - public static async Task OptionsAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Options, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static async Task OptionsAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Options, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } - - /// - /// 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. + /// Executes the request synchronously, authenticating if needed /// - /// RestClient instance - /// The request - /// Cancellation token - /// Expected result type - /// - public static async Task PatchAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Patch, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static async Task PatchAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Patch, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } - - /// - /// 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 - /// Cancellation token - /// Expected result type - /// - public static async Task DeleteAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Delete, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError().Data; - } - - public static async Task DeleteAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) { - var response = await client.ExecuteAsync(request, Method.Delete, cancellationToken).ConfigureAwait(false); - return response.ThrowIfError(); - } + /// Target deserialization type + /// + /// Request to be executed + /// Override the request method + public static RestResponse Execute(this IRestClient client, RestRequest request, Method httpMethod) + => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, httpMethod)); /// /// A specialized method to download files. @@ -302,6 +125,25 @@ public static async Task DeleteAsync(this IRestClient client, Rest return stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false); } + /// + /// A specialized method to download files as streams. + /// + /// + /// Pre-configured request instance. + /// The cancellation token + /// The downloaded stream. + [PublicAPI] + public static Stream? DownloadStream(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) + => AsyncHelpers.RunSync(() => client.DownloadStreamAsync(request, cancellationToken)); + + /// + /// A specialized method to download files. + /// + /// RestClient instance + /// Pre-configured request instance. + /// The downloaded file. + public static byte[]? DownloadData(this IRestClient client, RestRequest request) => AsyncHelpers.RunSync(() => client.DownloadDataAsync(request)); + /// /// Reads a stream returned by the specified endpoint, deserializes each line to JSON and returns each object asynchronously. /// It is required for each JSON object to be returned in a single line. @@ -342,5 +184,4 @@ [EnumeratorCancellation] CancellationToken cancellationToken yield return serializer.Deserializer.Deserialize(response)!; } } - } diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 28e2b1b0e..bc9330464 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -17,9 +17,6 @@ - - RestClient.Extensions.cs - RestClient.Extensions.cs @@ -35,6 +32,27 @@ PropertyCache.cs + + RestClient.Extensions.cs + + + RestClient.Extensions.cs + + + RestClient.Extensions.cs + + + RestClient.Extensions.cs + + + RestClient.Extensions.cs + + + RestClient.Extensions.cs + + + RestClient.Extensions.cs + diff --git a/src/RestSharp/Sync/AsyncHelpers.cs b/src/RestSharp/Sync/AsyncHelpers.cs deleted file mode 100644 index fb73c02fd..000000000 --- a/src/RestSharp/Sync/AsyncHelpers.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors -// -// 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 { - 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/Sync/RestClient.Sync.cs b/src/RestSharp/Sync/RestClient.Sync.cs deleted file mode 100644 index 50b4e6557..000000000 --- a/src/RestSharp/Sync/RestClient.Sync.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors -// -// 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. -// - -namespace RestSharp; - -public static partial class RestClientExtensions { - /// - /// Executes the request synchronously, authenticating if needed - /// - /// - /// Request to be executed - /// The cancellation token - public static RestResponse Execute(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) - => AsyncHelpers.RunSync(() => client.ExecuteAsync(request, cancellationToken)); - - /// - /// A specialized method to download files as streams. - /// - /// - /// Pre-configured request instance. - /// The cancellation token - /// The downloaded stream. - [PublicAPI] - public static Stream? DownloadStream(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) - => AsyncHelpers.RunSync(() => client.DownloadStreamAsync(request, cancellationToken)); -} diff --git a/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs b/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs deleted file mode 100644 index 25b265f09..000000000 --- a/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors -// -// 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. -// - -using System.Net; - -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. - /// - /// 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)); - - /// - /// 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 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 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 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)); -} \ No newline at end of file diff --git a/src/RestSharp/Sync/RestClientExtensions.Sync.cs b/src/RestSharp/Sync/RestClientExtensions.Sync.cs deleted file mode 100644 index 58be4e927..000000000 --- a/src/RestSharp/Sync/RestClientExtensions.Sync.cs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors -// -// 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. -// - -namespace RestSharp; - -[PublicAPI] -public static class RestClientSyncExtensions { - /// - /// 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 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 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 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 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 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 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 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 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)); - - /// - /// 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)); - - public static RestResponse Get(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.GetAsync(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. - /// - /// 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 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)); - - public static RestResponse Put(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.PutAsync(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. - /// - /// RestClient instance - /// The request - /// Expected result type - /// - public static T? Head(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.HeadAsync(request)); - - public static RestResponse Head(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.HeadAsync(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. - /// - /// RestClient instance - /// The request - /// Expected result type - /// - public static T? Options(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.OptionsAsync(request)); - - public static RestResponse Options(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.OptionsAsync(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. - /// - /// RestClient instance - /// The request - /// Expected result type - /// - [PublicAPI] - public static T? Patch(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.PatchAsync(request)); - - [PublicAPI] - public static RestResponse Patch(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.PatchAsync(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. - /// - /// RestClient instance - /// The request - /// Expected result type - /// - public static T? Delete(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.DeleteAsync(request)); - - public static RestResponse Delete(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.DeleteAsync(request)); - - /// - /// A specialized method to download files. - /// - /// RestClient instance - /// Pre-configured request instance. - /// The downloaded file. - public static byte[]? DownloadData(this RestClient client, RestRequest request) - => AsyncHelpers.RunSync(() => client.DownloadDataAsync(request)); -} From 2b032e2dd3b55fe209b63873dd7ef20adcca822a Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 4 Jul 2023 10:39:56 +0200 Subject: [PATCH 2/4] Remove empty file --- src/RestSharp/RestClient.Extensions.Json.cs | 21 --------------------- src/RestSharp/RestSharp.csproj | 3 --- 2 files changed, 24 deletions(-) delete mode 100644 src/RestSharp/RestClient.Extensions.Json.cs diff --git a/src/RestSharp/RestClient.Extensions.Json.cs b/src/RestSharp/RestClient.Extensions.Json.cs deleted file mode 100644 index 82e3fdf55..000000000 --- a/src/RestSharp/RestClient.Extensions.Json.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors -// -// 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. -// - -using System.Net; - -namespace RestSharp; - -public static partial class RestClientExtensions { -} diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 1d499fa19..25d60fe29 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -17,9 +17,6 @@ - - RestClient.Extensions.cs - RestClient.Extensions.cs From 6d9cf857953d46f1fa4cd1e52d384303e1603bc2 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 4 Jul 2023 10:58:39 +0200 Subject: [PATCH 3/4] Restore AlwaysMultipart functionality --- src/RestSharp/Request/RequestContent.cs | 67 ++++++++++++------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index 5a19f94fb..aefd6b578 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -36,47 +36,45 @@ public RequestContent(RestClient client, RestRequest request) { _parameters = new RequestParameters(_request.Parameters.Union(_client.DefaultParameters)); } - public HttpContent BuildContent() - { - var postParameters = _parameters.GetContentParameters(_request.Method).ToArray(); + public HttpContent BuildContent() { + var postParameters = _parameters.GetContentParameters(_request.Method).ToArray(); var postParametersExists = postParameters.Length > 0; var bodyParametersExists = _request.TryGetBodyParameter(out var bodyParameter); - var filesExists = _request.Files.Any(); + var filesExists = _request.Files.Any(); + + if (postParametersExists || + _request.HasFiles() || + BodyShouldBeMultipartForm(bodyParameter) || + filesExists || + _request.AlwaysMultipartFormData) { + Content = CreateMultipartFormDataContent(); + } - if (filesExists) - AddFiles(); + if (filesExists) AddFiles(); - if (bodyParametersExists) - AddBody(postParametersExists, bodyParameter!); + if (bodyParametersExists) AddBody(postParametersExists, bodyParameter!); - if (postParametersExists) - AddPostParameters(postParameters); + if (postParametersExists) AddPostParameters(postParameters); AddHeaders(); return Content!; } - - void AddFiles() - { - // File uploading without multipart/form-data - if (_request.AlwaysSingleFileAsContent && _request.Files.Count == 1) - { - var fileParameter = _request.Files.First(); - Content = ToStreamContent(fileParameter); - return; - } - - var mpContent = new MultipartFormDataContent(GetOrSetFormBoundary()); - - foreach (var fileParameter in _request.Files) - mpContent.Add(ToStreamContent(fileParameter)); - - Content = mpContent; - } - - StreamContent ToStreamContent(FileParameter fileParameter) - { + + void AddFiles() { + // File uploading without multipart/form-data + if (_request is { AlwaysSingleFileAsContent: true, Files.Count: 1 }) { + var fileParameter = _request.Files.First(); + Content?.Dispose(); + Content = ToStreamContent(fileParameter); + return; + } + + var mpContent = Content as MultipartFormDataContent; + foreach (var fileParameter in _request.Files) mpContent!.Add(ToStreamContent(fileParameter)); + } + + StreamContent ToStreamContent(FileParameter fileParameter) { var stream = fileParameter.GetFile(); _streams.Add(stream); var streamContent = new StreamContent(stream); @@ -123,7 +121,9 @@ HttpContent GetSerialized() { } } - static bool BodyShouldBeMultipartForm(BodyParameter bodyParameter) { + static bool BodyShouldBeMultipartForm(BodyParameter? bodyParameter) { + if (bodyParameter == null) return false; + var bodyContentType = bodyParameter.ContentType.OrValue(bodyParameter.Name); return bodyParameter.Name.IsNotEmpty() && bodyParameter.Name != bodyContentType; } @@ -139,8 +139,7 @@ MultipartFormDataContent CreateMultipartFormDataContent() { return mpContent; } - void AddBody(bool hasPostParameters, BodyParameter bodyParameter) - { + void AddBody(bool hasPostParameters, BodyParameter bodyParameter) { var bodyContent = Serialize(bodyParameter); // we need to send the body From 51f3c1961d33f33a7bb81b02fd5b13380721e95b Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 4 Jul 2023 12:57:45 +0200 Subject: [PATCH 4/4] Fix the Multipart form again as it became a default for all POST calls --- benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj | 3 ++- gen/SourceGenerator/SourceGenerator.csproj | 6 +++--- src/RestSharp/Request/RequestContent.cs | 3 +-- src/RestSharp/RestSharp.csproj | 2 +- .../RestSharp.Tests.Integrated.csproj | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj b/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj index c569e156e..8644bb21b 100644 --- a/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj +++ b/benchmarks/RestSharp.Benchmarks/RestSharp.Benchmarks.csproj @@ -9,7 +9,8 @@ - + + diff --git a/gen/SourceGenerator/SourceGenerator.csproj b/gen/SourceGenerator/SourceGenerator.csproj index 9e63fcfeb..29310d2dd 100644 --- a/gen/SourceGenerator/SourceGenerator.csproj +++ b/gen/SourceGenerator/SourceGenerator.csproj @@ -11,11 +11,11 @@ - - + + - + diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index aefd6b578..cfc7995ca 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -42,8 +42,7 @@ public HttpContent BuildContent() { var bodyParametersExists = _request.TryGetBodyParameter(out var bodyParameter); var filesExists = _request.Files.Any(); - if (postParametersExists || - _request.HasFiles() || + if (_request.HasFiles() || BodyShouldBeMultipartForm(bodyParameter) || filesExists || _request.AlwaysMultipartFormData) { diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj index 25d60fe29..a339d9944 100644 --- a/src/RestSharp/RestSharp.csproj +++ b/src/RestSharp/RestSharp.csproj @@ -3,7 +3,7 @@ true - + diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj index 6cf43fa35..faa763b3d 100644 --- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj +++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj @@ -17,7 +17,7 @@ - +