diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml
index 1e95432e0..26c0b4bf0 100644
--- a/.github/workflows/build-dev.yml
+++ b/.github/workflows/build-dev.yml
@@ -27,7 +27,7 @@ jobs:
name: Setup .NET
uses: actions/setup-dotnet@v3
with:
- dotnet-version: '7.0'
+ dotnet-version: '7.0.102'
-
name: Unshallow
run: git fetch --prune --unshallow
diff --git a/RestSharp.sln b/RestSharp.sln
index c81d31282..25439c208 100644
--- a/RestSharp.sln
+++ b/RestSharp.sln
@@ -35,6 +35,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestSharp.Serializers.CsvHe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestSharp.Tests.Serializers.Csv", "test\RestSharp.Tests.Serializers.Csv\RestSharp.Tests.Serializers.Csv.csproj", "{E6D94FFD-7811-40BE-ABC4-6D6AB41F0060}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGen", "SourceGen", "{55B8F371-B2BA-4DEE-AB98-5BAB8A21B1C2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerator", "gen\SourceGenerator\SourceGenerator.csproj", "{FE778406-ADCF-45A1-B775-A054B55BFC50}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug.Appveyor|Any CPU = Debug.Appveyor|Any CPU
@@ -444,6 +448,36 @@ Global
{E6D94FFD-7811-40BE-ABC4-6D6AB41F0060}.Release|x64.Build.0 = Release|Any CPU
{E6D94FFD-7811-40BE-ABC4-6D6AB41F0060}.Release|x86.ActiveCfg = Release|Any CPU
{E6D94FFD-7811-40BE-ABC4-6D6AB41F0060}.Release|x86.Build.0 = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|Any CPU.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|ARM.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|ARM.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|Mixed Platforms.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|x64.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|x64.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|x86.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug.Appveyor|x86.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|ARM.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|x64.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Debug|x86.Build.0 = Debug|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|ARM.ActiveCfg = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|ARM.Build.0 = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x64.ActiveCfg = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x64.Build.0 = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x86.ActiveCfg = Release|Any CPU
+ {FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -461,6 +495,7 @@ Global
{5A8A5BBE-28DA-4C89-B393-BE39A96E8DC0} = {9051DDA0-E563-45D5-9504-085EBAACF469}
{2150E333-8FDC-42A3-9474-1A3956D46DE8} = {8C7B43EB-2F93-483C-B433-E28F9386AD67}
{E6D94FFD-7811-40BE-ABC4-6D6AB41F0060} = {9051DDA0-E563-45D5-9504-085EBAACF469}
+ {FE778406-ADCF-45A1-B775-A054B55BFC50} = {55B8F371-B2BA-4DEE-AB98-5BAB8A21B1C2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {77FF357B-03FA-4FA5-A68F-BFBE5800FEBA}
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 3a9b04154..52aa3bf19 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -11,7 +11,7 @@ module.exports = {
themeConfig: {
logo: "/restsharp.png",
navbar: [
- {text: "Migration to v107", link: "/v107/"},
+ {text: "Migration from legacy", link: "/v107/"},
{text: "Documentation", link: "/intro.html"},
{text: "Get help", link: "/support/"},
{text: "NuGet", link: "https://nuget.org/packages/RestSharp"}
@@ -34,7 +34,7 @@ module.exports = {
"/v107/": [
{
text: "",
- header: "Migration to v107",
+ header: "Migration from legacy",
children: [
"/v107/README.md"
]
diff --git a/docs/README.md b/docs/README.md
index 0d4095451..4151ff852 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,7 +5,7 @@ heroText: RestSharp
tagline: Probably, the most popular REST API client library for .NET
actions:
- text: Get Started →
- link: /v107/
+ link: /intro.html
features:
- title: Serialization
details: JSON, XML and custom serialization and deserialization
@@ -24,7 +24,7 @@ footer: Apache 2.0 Licensed | Copyright (c) .NET Foundation and Contributors
RestSharp is probably the most popular HTTP client library for .NET. Featuring automatic serialization and deserialization, request and response type detection, variety of authentications and other useful features, it is being used by hundreds of thousands of projects.
-RestSharp passed over 165 million downloads on NuGet, with average daily download count close to 42,000. It's being used by many popular OSS projects, including Roslyn and Swagger.
+RestSharp passed over 190 million downloads on NuGet, with average daily download count over 43,000. It's being used by many popular OSS projects, including Roslyn and Swagger.
Supported by [AWS](https://aws.amazon.com/developer/language/net/solutions/).

diff --git a/docs/usage.md b/docs/usage.md
index b1901f1ae..459d81165 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -457,6 +457,28 @@ First, there's `DownloadDataAsync`, which returns `Task`. This function allows you to open a stream reader and asynchronously stream large responses to memory or disk.
+
+## Reusing HttpClient
+
+RestSharp uses `HttpClient` internally to make HTTP requests. It's possible to reuse the same `HttpClient` instance for multiple `RestClient` instances. This is useful when you want to share the same connection pool between multiple `RestClient` instances.
+
+One way of doing it is to use `RestClient` constructors that accept an instance of `HttpClient` or `HttpMessageHandler` as an argument. Note that in that case not all the options provided via `RestClientOptions` will be used. Here is the list of options that will work:
+
+- `BaseAddress` will be used to set the base address of the `HttpClient` instance if base address is not set there already.
+- `MaxTimeout`
+- `UserAgent` will be set if the `User-Agent` header is not set on the `HttpClient` instance already.
+- `Expect100Continue`
+
+Another option is to use a simple HTTP client factory. It is a static factory, which holds previously instantiated `HttpClient` instances. It can be used to create `RestClient` instances that share the same `HttpClient` instance. The cache key is the `BaseUrl` provided in the options. When you opt-in to use the factory and don't set `BaseUrl`, the `RestClient` constructor will crash.
+
+```csharp
+var client = new RestClient(new Uri("https://example.org/api"), useClientFactory: true);
+```
+
+::: warning
+Note that the `RestClient` constructor will not reconfigure the `HttpClient` instance if it's already in the cache. Therefore, you should not try using the factory when providing different options for the same base URL.
+:::
+
## Blazor support
Inside a Blazor webassembly app, you can make requests to external API endpoints. Microsoft examples show how to do it with `HttpClient`, and it's also possible to use RestSharp for the same purpose.
diff --git a/docs/v107/README.md b/docs/v107/README.md
index 87d7771b7..8088c5ae8 100644
--- a/docs/v107/README.md
+++ b/docs/v107/README.md
@@ -1,10 +1,10 @@
---
-title: RestSharp Next (v107)
+title: RestSharp Next (v107+)
---
-## RestSharp v107
+## RestSharp v107+
-The latest version of RestSharp is v107. It's a major upgrade, which contains quite a few breaking changes.
+RestSharp got a major upgrade in v107, which contains quite a few breaking changes.
The most important change is that RestSharp stop using the legacy `HttpWebRequest` class, and uses well-known 'HttpClient' instead.
This move solves lots of issues, like hanging connections due to improper `HttpClient` instance cache, updated protocols support, and many other problems.
@@ -17,7 +17,7 @@ Finally, most of the interfaces are now gone.
### RestClient and options
-The `IRestClient` interface is deprecated. You will be using the `RestClient` class instance.
+The `IRestClient` interface is deprecated in v107, but brought back in v109. The new interface, however, has a much smaller API compared to previous versions. You will be using the `RestClient` class instance.
Most of the client options are moved to `RestClientOptions`. If you can't find the option you used to set on `IRestClient`, check the options, it's probably there.
@@ -187,23 +187,16 @@ The next RestSharp version presumably solves the following issues:
## Deprecated interfaces
The following interfaces are removed from RestSharp:
-- `IRestClient`
- `IRestRequest`
- `IRestResponse`
- `IHttp`
-### Motivation
-
-All the deprecated interfaces had only one implementation in RestSharp, so those interfaces were abstracting nothing. It is now unclear what was the purpose for adding those interfaces initially.
-
-What about mocking it, you might ask? The answer is: what would you do if you use a plain `HttpClient` instance? It doesn't implement any interface for the same reason - there's nothing to abstract, and there's only one implementation. We don't recommend mocking `RestClient` in your tests when you are testing against APIs that are controlled by you or people in your organisation. Test your clients against the real thing, as REST calls are I/O-bound. Mocking REST calls is like mocking database calls, and lead to a lot of issues in production even if all your tests pass against mocks.
-
-As mentioned in [Recommended usage](#recommended-usage), we advise against using `RestClient` in the application code, and advocate wrapping it inside particular API client classes. Those classes would be under your control, and you are totally free to use interfaces there. If you absolutely must mock, you can mock your interfaces instead.
-
### Mocking
Mocking an infrastructure component like RestSharp (or HttpClient) is not the best idea. Even if you check that all the parameters are added correctly to the request, your "unit test" will only give you a false sense of safety that your code actually works. But, you have no guarantee that the remote server will accept your request, or if you can handle the actual response correctly.
+However, since v109 you can still mock the `IRestClient` interface, but you only need to implement the `ExecuteAsync` method. The `ExecuteAsync` method is the only one that actually makes a call to the remote server. All other methods are just wrappers around it.
+
The best way to test HTTP calls is to make some, using the actual service you call. However, you might still want to check if your API client forms requests in a certain way. You might also be sure about what the remote server responds to your calls with, so you can build a set of JSON (or XML) responses, so you can simulate remote calls.
It is perfectly doable without using interfaces. As RestSharp uses `HttpClient` internally, it certainly uses `HttpMessageHandler`. Features like delegating handlers allow you to intercept the request pipeline, inspect the request, and substitute the response. You can do it yourself, or use a library like [MockHttp](https://github.com/richardszalay/mockhttp). They have an example provided in the repository README, so we have changed it for RestClient here:
diff --git a/docs/v107/interfaces.md b/docs/v107/interfaces.md
index 9c4c5486b..62991fff5 100644
--- a/docs/v107/interfaces.md
+++ b/docs/v107/interfaces.md
@@ -1,58 +1,57 @@
-| `IRestClient` member | Where is it now? |
-|:------------------------------------------------------------------------------------------------|:-----------------------------------|
-| `CookieContainer` | `RestClient` |
-| `AutomaticDecompression` | `RestClientOptions`, changed type |
-| `MaxRedirects` | `RestClientOptions` |
-| `UserAgent` | `RestClientOptions` |
-| `Timeout` | `RestClientOptions`, `RestRequest` |
-| `Authenticator` | `RestClient` |
-| `BaseUrl` | `RestClientOptions` |
-| `Encoding` | `RestClientOptions` |
-| `ThrowOnDeserializationError` | `RestClientOptions` |
-| `FailOnDeserializationError` | `RestClientOptions` |
-| `ThrowOnAnyError` | `RestClientOptions` |
-| `PreAuthenticate` | `RestClientOptions` |
-| `BaseHost` | `RestClientOptions` |
-| `AllowMultipleDefaultParametersWithSameName` | `RestClientOptions` |
-| `ClientCertificates` | `RestClientOptions` |
-| `Proxy` | `RestClientOptions` |
-| `CachePolicy` | `RestClientOptions`, changed type |
-| `FollowRedirects` | `RestClientOptions` |
-| `RemoteCertificateValidationCallback` | `RestClientOptions` |
-| `Pipelined` | Not supported |
-| `UnsafeAuthenticatedConnectionSharing` | Not supported |
-| `ConnectionGroupName` | Not supported |
-| `ReadWriteTimeout` | Not supported |
-| `UseSynchronizationContext` | Not supported |
-| `DefaultParameters` | `RestClient` |
-| `UseSerializer(Func serializerFactory)` | `RestClient` |
-| `UseSerializer()` | `RestClient` |
-| `Deserialize(IRestResponse response)` | `RestClient` |
-| `BuildUri(IRestRequest request)` | `RestClient` |
-| `UseUrlEncoder(Func encoder)` | Extension |
-| `UseQueryEncoder(Func queryEncoder)` | Extension |
-| `ExecuteAsync(IRestRequest request, CancellationToken cancellationToken)` | `RestClient` |
-| `ExecuteAsync(IRestRequest request, Method httpMethod, CancellationToken cancellationToken)` | Extension |
-| `ExecuteAsync(IRestRequest request, Method httpMethod, CancellationToken cancellationToken)` | Extension |
-| `ExecuteAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
-| `ExecuteGetAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
-| `ExecutePostAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
-| `ExecuteGetAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
-| `ExecutePostAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
-| `Execute(IRestRequest request)` | Deprecated |
-| `Execute(IRestRequest request, Method httpMethod)` | Deprecated |
-| `Execute(IRestRequest request)` | Deprecated |
-| `Execute(IRestRequest request, Method httpMethod)` | Deprecated |
-| `DownloadData(IRestRequest request)` | Deprecated |
-| `ExecuteAsGet(IRestRequest request, string httpMethod)` | Deprecated |
-| `ExecuteAsPost(IRestRequest request, string httpMethod)` | Deprecated |
-| `ExecuteAsGet(IRestRequest request, string httpMethod)` | Deprecated |
-| `ExecuteAsPost(IRestRequest request, string httpMethod)` | Deprecated |
-| `BuildUriWithoutQueryParameters(IRestRequest request)` | Removed |
-| `ConfigureWebRequest(Action configurator)` | Removed |
-| `AddHandler(string contentType, Func deserializerFactory)` | Removed |
-| `RemoveHandler(string contentType)` | Removed |
-| `ClearHandlers()` | Removed |
+| `IRestClient` member | Where is it now? |
+|:------------------------------------------------------------------------------------------------|:-------------------------------------------|
+| `CookieContainer` | Not supported, use `RestRequest.AddCookie` |
+| `AutomaticDecompression` | `RestClientOptions`, changed type |
+| `MaxRedirects` | `RestClientOptions` |
+| `UserAgent` | `RestClientOptions` |
+| `Timeout` | `RestClientOptions`, `RestRequest` |
+| `Authenticator` | `RestClient` |
+| `BaseUrl` | `RestClientOptions` |
+| `Encoding` | `RestClientOptions` |
+| `ThrowOnDeserializationError` | `RestClientOptions` |
+| `FailOnDeserializationError` | `RestClientOptions` |
+| `ThrowOnAnyError` | `RestClientOptions` |
+| `PreAuthenticate` | `RestClientOptions` |
+| `BaseHost` | `RestClientOptions` |
+| `AllowMultipleDefaultParametersWithSameName` | `RestClientOptions` |
+| `ClientCertificates` | `RestClientOptions` |
+| `Proxy` | `RestClientOptions` |
+| `CachePolicy` | `RestClientOptions`, changed type |
+| `FollowRedirects` | `RestClientOptions` |
+| `RemoteCertificateValidationCallback` | `RestClientOptions` |
+| `Pipelined` | Not supported |
+| `UnsafeAuthenticatedConnectionSharing` | Not supported |
+| `ConnectionGroupName` | Not supported |
+| `ReadWriteTimeout` | Not supported |
+| `UseSynchronizationContext` | Not supported |
+| `DefaultParameters` | `RestClient` |
+| `UseSerializer(Func serializerFactory)` | `RestClient` |
+| `UseSerializer()` | `RestClient` |
+| `Deserialize(IRestResponse response)` | `RestClient` |
+| `UseUrlEncoder(Func encoder)` | `RestClientOptions.Encode` |
+| `UseQueryEncoder(Func queryEncoder)` | `RestClientOptions.EncodeQuery` |
+| `ExecuteAsync(IRestRequest request, CancellationToken cancellationToken)` | `RestClient` |
+| `ExecuteAsync(IRestRequest request, Method httpMethod, CancellationToken cancellationToken)` | Extension |
+| `ExecuteAsync(IRestRequest request, Method httpMethod, CancellationToken cancellationToken)` | Extension |
+| `ExecuteAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
+| `ExecuteGetAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
+| `ExecutePostAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
+| `ExecuteGetAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
+| `ExecutePostAsync(IRestRequest request, CancellationToken cancellationToken)` | Extension |
+| `Execute(IRestRequest request)` | Extension |
+| `Execute(IRestRequest request, Method httpMethod)` | Extension |
+| `Execute(IRestRequest request)` | Extension |
+| `Execute(IRestRequest request, Method httpMethod)` | Extension |
+| `DownloadData(IRestRequest request)` | Extension |
+| `ExecuteAsGet(IRestRequest request, string httpMethod)` | Extension |
+| `ExecuteAsPost(IRestRequest request, string httpMethod)` | Extension |
+| `ExecuteAsGet(IRestRequest request, string httpMethod)` | Extension |
+| `ExecuteAsPost(IRestRequest request, string httpMethod)` | Extension |
+| `BuildUriWithoutQueryParameters(IRestRequest request)` | Removed |
+| `ConfigureWebRequest(Action configurator)` | Removed |
+| `AddHandler(string contentType, Func deserializerFactory)` | Removed |
+| `RemoveHandler(string contentType)` | Removed |
+| `ClearHandlers()` | Removed |
| `IRestRequest` member | Where is it now? |
|:-------------------------------------------------------------------------------------------------------|:---------------------------------|
diff --git a/gen/SourceGenerator/ImmutableGenerator.cs b/gen/SourceGenerator/ImmutableGenerator.cs
new file mode 100644
index 000000000..367560d68
--- /dev/null
+++ b/gen/SourceGenerator/ImmutableGenerator.cs
@@ -0,0 +1,92 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace SourceGenerator;
+
+[Generator]
+public class ImmutableGenerator : ISourceGenerator {
+ public void Initialize(GeneratorInitializationContext context) { }
+
+ public void Execute(GeneratorExecutionContext context) {
+ var compilation = context.Compilation;
+
+ var mutableClasses = compilation.SyntaxTrees
+ .Select(tree => compilation.GetSemanticModel(tree))
+ .SelectMany(model => model.SyntaxTree.GetRoot().DescendantNodes().OfType())
+ .Where(syntax => syntax.AttributeLists.Any(list => list.Attributes.Any(attr => attr.Name.ToString() == "GenerateImmutable")));
+
+ foreach (var mutableClass in mutableClasses) {
+ var immutableClass = GenerateImmutableClass(mutableClass, compilation);
+ context.AddSource($"ReadOnly{mutableClass.Identifier.Text}.cs", SourceText.From(immutableClass, Encoding.UTF8));
+ }
+ }
+
+ static string GenerateImmutableClass(TypeDeclarationSyntax mutableClass, Compilation compilation) {
+ var containingNamespace = compilation.GetSemanticModel(mutableClass.SyntaxTree).GetDeclaredSymbol(mutableClass)!.ContainingNamespace;
+
+ var namespaceName = containingNamespace.ToDisplayString();
+
+ var className = mutableClass.Identifier.Text;
+
+ var usings = mutableClass.SyntaxTree.GetCompilationUnitRoot().Usings.Select(u => u.ToString());
+
+ var properties = GetDefinitions(SyntaxKind.SetKeyword)
+ .Select(prop => $" public {prop.Type} {prop.Identifier.Text} {{ get; }}")
+ .ToArray();
+
+ var props = GetDefinitions(SyntaxKind.SetKeyword).ToArray();
+
+ const string argName = "inner";
+ var mutableProperties = props
+ .Select(prop => $" {prop.Identifier.Text} = {argName}.{prop.Identifier.Text};");
+
+ var constructor = $@" public ReadOnly{className}({className} {argName}) {{
+{string.Join("\n", mutableProperties)}
+ }}";
+
+ const string template = @"{Usings}
+
+namespace {Namespace};
+
+public class ReadOnly{ClassName} {
+{Constructor}
+
+{Properties}
+}";
+
+ var code = template
+ .Replace("{Usings}", string.Join("\n", usings))
+ .Replace("{Namespace}", namespaceName)
+ .Replace("{ClassName}", className)
+ .Replace("{Constructor}", constructor)
+ .Replace("{Properties}", string.Join("\n", properties));
+
+ return code;
+
+ IEnumerable GetDefinitions(SyntaxKind kind)
+ => mutableClass.Members
+ .OfType()
+ .Where(
+ prop =>
+ prop.AccessorList!.Accessors.Any(accessor => accessor.Keyword.IsKind(kind))
+ );
+ }
+}
diff --git a/gen/SourceGenerator/SourceGenerator.csproj b/gen/SourceGenerator/SourceGenerator.csproj
new file mode 100644
index 000000000..6b640d2f8
--- /dev/null
+++ b/gen/SourceGenerator/SourceGenerator.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netstandard2.0
+ 11
+ enable
+ disable
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 727121d98..e38e2e8eb 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,7 +1,7 @@
- netstandard2.0;net5.0;net6.0
+ netstandard2.0;net471;net6.0;net7.0
restsharp.png
Apache-2.0
https://restsharp.dev
@@ -15,6 +15,7 @@
snupkg
true
$(NoWarn);1591
+ 11
diff --git a/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs b/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs
index cc2c91d5a..318139ef0 100644
--- a/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs
+++ b/src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs
@@ -16,11 +16,11 @@ public class CsvHelperSerializer : IDeserializer, IRestSerializer, ISerializer {
public string[] AcceptedContentTypes => new[] { TextCsvContentType, "application/x-download" };
- public SupportsContentType SupportsContentType => x => Array.IndexOf(AcceptedContentTypes, x) != -1 || x.Contains("csv");
+ public SupportsContentType SupportsContentType => x => Array.IndexOf(AcceptedContentTypes, x) != -1 || x.Value.Contains("csv");
public DataFormat DataFormat => DataFormat.None;
- public string ContentType { get; set; } = TextCsvContentType;
+ public ContentType ContentType { get; set; } = TextCsvContentType;
public CsvHelperSerializer() => _configuration = new CsvConfiguration(CultureInfo.InvariantCulture);
diff --git a/src/RestSharp.Serializers.CsvHelper/RestClientExtensions.cs b/src/RestSharp.Serializers.CsvHelper/RestClientExtensions.cs
index 0b123bc65..d71b6b38a 100644
--- a/src/RestSharp.Serializers.CsvHelper/RestClientExtensions.cs
+++ b/src/RestSharp.Serializers.CsvHelper/RestClientExtensions.cs
@@ -4,8 +4,8 @@ namespace RestSharp.Serializers.CsvHelper;
[PublicAPI]
public static class RestClientExtensions {
- public static RestClient UseCsvHelper(this RestClient client) => client.UseSerializer();
+ public static SerializerConfig UseCsvHelper(this SerializerConfig config) => config.UseSerializer();
- public static RestClient UseCsvHelper(this RestClient client, CsvConfiguration configuration)
- => client.UseSerializer(() => new CsvHelperSerializer(configuration));
+ public static SerializerConfig UseCsvHelper(this SerializerConfig config, CsvConfiguration configuration)
+ => config.UseSerializer(() => new CsvHelperSerializer(configuration));
}
diff --git a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj
index 7d50d166b..2168d422a 100644
--- a/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj
+++ b/src/RestSharp.Serializers.CsvHelper/RestSharp.Serializers.CsvHelper.csproj
@@ -5,4 +5,7 @@
+
+
+
diff --git a/src/RestSharp.Serializers.NewtonsoftJson/JsonNetSerializer.cs b/src/RestSharp.Serializers.NewtonsoftJson/JsonNetSerializer.cs
index 5f3a019af..61a08116a 100644
--- a/src/RestSharp.Serializers.NewtonsoftJson/JsonNetSerializer.cs
+++ b/src/RestSharp.Serializers.NewtonsoftJson/JsonNetSerializer.cs
@@ -34,7 +34,7 @@ public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer {
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};
- [ThreadStatic] static WriterBuffer? _writerBuffer;
+ [ThreadStatic] static WriterBuffer? writerBuffer;
readonly JsonSerializer _serializer;
@@ -52,7 +52,7 @@ public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer {
public string? Serialize(object? obj) {
if (obj == null) return null;
- using var writerBuffer = _writerBuffer ??= new WriterBuffer(_serializer);
+ using var writerBuffer = JsonNetSerializer.writerBuffer ??= new WriterBuffer(_serializer);
_serializer.Serialize(writerBuffer.GetJsonTextWriter(), obj, obj.GetType());
@@ -73,11 +73,11 @@ public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer {
public ISerializer Serializer => this;
public IDeserializer Deserializer => this;
- public string[] AcceptedContentTypes => Serializers.ContentType.JsonAccept;
+ public string[] AcceptedContentTypes => RestSharp.ContentType.JsonAccept;
- public string ContentType { get; set; } = "application/json";
+ public ContentType ContentType { get; set; } = ContentType.Json;
- public SupportsContentType SupportsContentType => contentType => contentType.Contains("json");
+ public SupportsContentType SupportsContentType => contentType => contentType.Value.Contains("json");
public DataFormat DataFormat => DataFormat.Json;
}
\ No newline at end of file
diff --git a/src/RestSharp.Serializers.NewtonsoftJson/RestClientExtensions.cs b/src/RestSharp.Serializers.NewtonsoftJson/RestClientExtensions.cs
index 2d02d58ec..f228ac07f 100644
--- a/src/RestSharp.Serializers.NewtonsoftJson/RestClientExtensions.cs
+++ b/src/RestSharp.Serializers.NewtonsoftJson/RestClientExtensions.cs
@@ -19,16 +19,16 @@ public static class RestClientExtensions {
///
/// Use Newtonsoft.Json serializer with default settings
///
- ///
+ ///
///
- public static RestClient UseNewtonsoftJson(this RestClient client) => client.UseSerializer(() => new JsonNetSerializer());
+ public static SerializerConfig UseNewtonsoftJson(this SerializerConfig config) => config.UseSerializer(() => new JsonNetSerializer());
///
/// Use Newtonsoft.Json serializer with custom settings
///
- ///
+ ///
/// Newtonsoft.Json serializer settings
///
- public static RestClient UseNewtonsoftJson(this RestClient client, JsonSerializerSettings settings)
- => client.UseSerializer(() => new JsonNetSerializer(settings));
+ public static SerializerConfig UseNewtonsoftJson(this SerializerConfig config, JsonSerializerSettings settings)
+ => config.UseSerializer(() => new JsonNetSerializer(settings));
}
\ No newline at end of file
diff --git a/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj b/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj
index dc5e238b4..d82c731bc 100644
--- a/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj
+++ b/src/RestSharp.Serializers.NewtonsoftJson/RestSharp.Serializers.NewtonsoftJson.csproj
@@ -7,5 +7,6 @@
+
diff --git a/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj b/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj
index aa523d350..1d6c8eaab 100644
--- a/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj
+++ b/src/RestSharp.Serializers.Xml/RestSharp.Serializers.Xml.csproj
@@ -5,4 +5,7 @@
+
+
+
diff --git a/src/RestSharp.Serializers.Xml/XmlSerializer.cs b/src/RestSharp.Serializers.Xml/XmlSerializer.cs
index 6e90907d7..e31e9f067 100644
--- a/src/RestSharp.Serializers.Xml/XmlSerializer.cs
+++ b/src/RestSharp.Serializers.Xml/XmlSerializer.cs
@@ -27,13 +27,13 @@ public class XmlSerializer : IXmlSerializer, IWithRootElement, IWithDateFormat {
///
/// Default constructor, does not specify namespace
///
- public XmlSerializer() => ContentType = Serializers.ContentType.Xml;
+ public XmlSerializer() { }
///
/// Specify the namespaced to be used when serializing
///
/// XML namespace
- public XmlSerializer(string @namespace) : this() => Namespace = @namespace;
+ public XmlSerializer(string @namespace) => Namespace = @namespace;
///
/// Serialize the object as XML
@@ -103,7 +103,7 @@ public string Serialize(object obj) {
///
/// Content type for serialized content
///
- public string ContentType { get; set; }
+ public ContentType ContentType { get; set; } = ContentType.Xml;
void Map(XContainer root, object obj) {
var objType = obj.GetType();
diff --git a/src/RestSharp.Serializers.Xml/XmlSerializerClientExtensions.cs b/src/RestSharp.Serializers.Xml/XmlSerializerClientExtensions.cs
index ebc86760a..72a651a0f 100644
--- a/src/RestSharp.Serializers.Xml/XmlSerializerClientExtensions.cs
+++ b/src/RestSharp.Serializers.Xml/XmlSerializerClientExtensions.cs
@@ -16,11 +16,11 @@ namespace RestSharp.Serializers.Xml;
[PublicAPI]
public static class XmlSerializerClientExtensions {
- public static RestClient UseXmlSerializer(
- this RestClient restClient,
- string? xmlNamespace = null,
- string? rootElement = null,
- bool useAttributeDeserializer = false
+ public static SerializerConfig UseXmlSerializer(
+ this SerializerConfig config,
+ string? xmlNamespace = null,
+ string? rootElement = null,
+ bool useAttributeDeserializer = false
) {
var xmlSerializer = new XmlSerializer {
Namespace = xmlNamespace,
@@ -33,6 +33,6 @@ public static RestClient UseXmlSerializer(
.WithXmlSerializer(xmlSerializer)
.WithXmlDeserializer(xmlDeserializer);
- return restClient.UseSerializer(() => serializer);
+ return config.UseSerializer(() => serializer);
}
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs b/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
index 14f743d81..05e515f72 100644
--- a/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
+++ b/src/RestSharp/Authenticators/OAuth/OAuth1Authenticator.cs
@@ -263,7 +263,7 @@ string GetAuthorizationHeader() {
if (!Realm.IsEmpty())
oathParameters.Insert(0, $"realm=\"{OAuthTools.UrlEncodeRelaxed(Realm!)}\"");
- return "OAuth " + string.Join(",", oathParameters);
+ return $"OAuth {string.Join(",", oathParameters)}";
}
}
}
diff --git a/src/RestSharp/ContentType.cs b/src/RestSharp/ContentType.cs
new file mode 100644
index 000000000..1a2d3d36d
--- /dev/null
+++ b/src/RestSharp/ContentType.cs
@@ -0,0 +1,87 @@
+// 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.Http.Headers;
+
+namespace RestSharp;
+
+public delegate bool SupportsContentType(ContentType contentType);
+
+public class ContentType : IEquatable {
+ ContentType(string contentType) {
+ var ct = Ensure.NotEmptyString(contentType, nameof(contentType));
+ if (!ct.Contains('/')) throw new ArgumentException("Invalid content type string", nameof(contentType));
+
+ _value = ct;
+ }
+
+ public static readonly ContentType Json = "application/json";
+ public static readonly ContentType Xml = "application/xml";
+ public static readonly ContentType Plain = "text/plain";
+ public static readonly ContentType Binary = "application/octet-stream";
+ public static readonly ContentType GZip = "application/x-gzip";
+ public static readonly ContentType FormUrlEncoded = "application/x-www-form-urlencoded";
+ public static readonly ContentType Undefined = "undefined/undefined";
+
+ public string Value => _value == Undefined._value ? Plain._value : _value;
+
+ public static ContentType FromDataFormat(DataFormat dataFormat) => DataFormatMap[dataFormat];
+
+ public override string ToString() => Value;
+
+ public static implicit operator ContentType(string? contentType) => contentType == null ? Undefined : new(contentType);
+
+ public static implicit operator string(ContentType contentType) => contentType.Value;
+
+ public ContentType Or(ContentType? contentType) => Equals(Undefined) ? (contentType ?? Plain) : this;
+
+ public string OrValue(string? contentType) => Equals(Undefined) ? (contentType ?? Plain.Value) : Value;
+
+ public MediaTypeHeaderValue AsMediaTypeHeaderValue => MediaTypeHeaderValue.Parse(Value);
+
+ static readonly Dictionary DataFormatMap =
+ new() {
+ { DataFormat.Xml, Xml },
+ { DataFormat.Json, Json },
+ { DataFormat.None, Plain },
+ { DataFormat.Binary, Binary }
+ };
+
+ public static readonly string[] JsonAccept = {
+ "application/json", "text/json", "text/x-json", "text/javascript"
+ };
+
+ public static readonly string[] XmlAccept = {
+ "application/xml", "text/xml"
+ };
+
+ readonly string _value;
+
+ public bool Equals(ContentType? other) {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return _value == other._value;
+ }
+
+ public override bool Equals(object? obj) {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+
+ return Equals((ContentType)obj);
+ }
+
+ public override int GetHashCode() => _value.GetHashCode();
+}
diff --git a/src/RestSharp/Ensure.cs b/src/RestSharp/Ensure.cs
index 48da6015e..2153c3944 100644
--- a/src/RestSharp/Ensure.cs
+++ b/src/RestSharp/Ensure.cs
@@ -15,14 +15,13 @@
namespace RestSharp;
static class Ensure {
- public static T NotNull(T? value, string name)
- => value ?? throw new ArgumentNullException(name);
+ public static T NotNull(T? value, [InvokerParameterName] string name) => value ?? throw new ArgumentNullException(name);
- public static string NotEmpty(string? value, string name)
+ public static string NotEmpty(string? value, [InvokerParameterName] string name)
=> string.IsNullOrWhiteSpace(value) ? throw new ArgumentNullException(name) : value!;
- public static string NotEmptyString(object? value, string name) {
+ public static string NotEmptyString(object? value, [InvokerParameterName] string name) {
var s = value as string ?? value?.ToString();
return string.IsNullOrWhiteSpace(s) ? throw new ArgumentNullException(name) : s!;
}
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/Enum.cs b/src/RestSharp/Enum.cs
index e046d4864..fd584bc8c 100644
--- a/src/RestSharp/Enum.cs
+++ b/src/RestSharp/Enum.cs
@@ -11,6 +11,7 @@
// 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;
@@ -19,9 +20,44 @@ namespace RestSharp;
///
public enum ParameterType {
///
- /// Cookie parameter
+ /// A that will added to the QueryString for GET, DELETE, OPTIONS and HEAD requests; and form for POST and PUT requests.
///
- GetOrPost, UrlSegment, HttpHeader, RequestBody, QueryString
+ ///
+ /// See .
+ ///
+ GetOrPost,
+
+ ///
+ /// A that will be added to part of the url by replacing a {placeholder} within the absolute path.
+ ///
+ ///
+ /// See .
+ ///
+ UrlSegment,
+
+ ///
+ /// A that will be added as a request header
+ ///
+ ///
+ /// See .
+ ///
+ HttpHeader,
+
+ ///
+ /// A that will be added to the request body
+ ///
+ ///
+ /// See .
+ ///
+ RequestBody,
+
+ ///
+ /// A that will be added to the query string
+ ///
+ ///
+ /// See .
+ ///
+ QueryString
}
///
@@ -55,4 +91,29 @@ public struct DateFormat {
///
/// Status for responses (surprised?)
///
-public enum ResponseStatus { None, Completed, Error, TimedOut, Aborted }
\ No newline at end of file
+public enum ResponseStatus {
+ ///
+ /// Not Applicable, for when the Request has not yet been made
+ ///
+ None,
+
+ ///
+ /// for when the request is passes as a result of being true, or when the response is
+ ///
+ Completed,
+
+ ///
+ /// for when the request fails due as a result of being false except for the case when the response is
+ ///
+ Error,
+
+ ///
+ /// for when the Operation is cancelled due to the request taking longer than the length of time prescribed by or due to the timing out.
+ ///
+ TimedOut,
+
+ ///
+ /// for when the Operation is cancelled, due to reasons other than
+ ///
+ Aborted
+}
\ No newline at end of file
diff --git a/src/RestSharp/NameValuePair.cs b/src/RestSharp/Extensions/GenerateImmutableAttribute.cs
similarity index 63%
rename from src/RestSharp/NameValuePair.cs
rename to src/RestSharp/Extensions/GenerateImmutableAttribute.cs
index 6b281c26a..fcd8c546d 100644
--- a/src/RestSharp/NameValuePair.cs
+++ b/src/RestSharp/Extensions/GenerateImmutableAttribute.cs
@@ -1,30 +1,19 @@
// 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;
+namespace RestSharp.Extensions;
-[PublicAPI]
-public class NameValuePair {
- public static NameValuePair Empty = new(null, null);
-
- public NameValuePair(string? name, string? value) {
- Name = name;
- Value = value;
- }
-
- public string? Name { get; }
- public string? Value { get; }
-
- public bool IsEmpty => Name == null;
-}
\ No newline at end of file
+[AttributeUsage(AttributeTargets.Class)]
+public class GenerateImmutableAttribute : Attribute { }
diff --git a/src/RestSharp/Extensions/HttpResponseExtensions.cs b/src/RestSharp/Extensions/HttpResponseExtensions.cs
index 53b873e6c..4558f2d7a 100644
--- a/src/RestSharp/Extensions/HttpResponseExtensions.cs
+++ b/src/RestSharp/Extensions/HttpResponseExtensions.cs
@@ -19,7 +19,7 @@ public static class HttpResponseExtensions {
internal static Exception? MaybeException(this HttpResponseMessage httpResponse)
=> httpResponse.IsSuccessStatusCode
? null
-#if NETSTANDARD
+#if NETSTANDARD || NETFRAMEWORK
: new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}");
#else
: new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}", null, httpResponse.StatusCode);
diff --git a/src/RestSharp/Extensions/ImmutableGenerator.cs b/src/RestSharp/Extensions/ImmutableGenerator.cs
new file mode 100644
index 000000000..e25e19183
--- /dev/null
+++ b/src/RestSharp/Extensions/ImmutableGenerator.cs
@@ -0,0 +1,48 @@
+// 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.Linq.Expressions;
+using System.Reflection;
+
+namespace RestSharp.Extensions;
+
+static class ImmutableGenerator {
+ static readonly MethodInfo SetPropertyMethod =
+ typeof(ImmutableGenerator).GetMethod(nameof(SetProperty), BindingFlags.Static | BindingFlags.NonPublic)!;
+
+ static void SetProperty(T obj, PropertyInfo property, object value) => property.SetValue(obj, (T)value);
+
+ public static Func CreateImmutableFunc()
+ where TMutable : class
+ where TImmutable : class, new()
+ {
+ var mutableType = typeof(TMutable);
+ var immutableType = typeof(TImmutable);
+
+ var parameter = Expression.Parameter(mutableType, "mutable");
+
+ var bindings = mutableType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Select(property =>
+ Expression.Bind(
+ immutableType.GetProperty(property.Name)!,
+ Expression.Property(parameter, property)));
+
+ var body = Expression.MemberInit(Expression.New(immutableType), bindings);
+
+ var lambda = Expression.Lambda>(body, parameter);
+
+ return lambda.Compile();
+ }
+}
diff --git a/src/RestSharp/Extensions/StreamExtensions.cs b/src/RestSharp/Extensions/StreamExtensions.cs
index b007b1c83..430437080 100644
--- a/src/RestSharp/Extensions/StreamExtensions.cs
+++ b/src/RestSharp/Extensions/StreamExtensions.cs
@@ -30,7 +30,7 @@ public static async Task ReadAsBytes(this Stream input, CancellationToke
using var ms = new MemoryStream();
int read;
-#if NETSTANDARD
+#if NETSTANDARD || NETFRAMEWORK
while ((read = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
#else
while ((read = await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0)
diff --git a/src/RestSharp/Extensions/StringExtensions.cs b/src/RestSharp/Extensions/StringExtensions.cs
index e5b343efa..cddbd3670 100644
--- a/src/RestSharp/Extensions/StringExtensions.cs
+++ b/src/RestSharp/Extensions/StringExtensions.cs
@@ -17,7 +17,7 @@
using System.Text.RegularExpressions;
using System.Web;
-namespace RestSharp.Extensions;
+namespace RestSharp.Extensions;
static class StringExtensions {
static readonly Regex IsUpperCaseRegex = new(@"^[A-Z]+$");
@@ -33,20 +33,19 @@ static class StringExtensions {
static readonly Regex AddSpacesRegex1 = new(@"[-\s]");
static readonly Regex AddSpacesRegex2 = new(@"([a-z\d])([A-Z])");
static readonly Regex AddSpacesRegex3 = new(@"([A-Z]+)([A-Z][a-z])");
- public static string UrlDecode(this string input) => HttpUtility.UrlDecode(input);
+
+ internal static string UrlDecode(this string input) => HttpUtility.UrlDecode(input);
///
/// Uses Uri.EscapeDataString() based on recommendations on MSDN
/// http://blogs.msdn.com/b/yangxind/archive/2006/11/09/don-t-use-net-system-uri-unescapedatastring-in-url-decoding.aspx
///
- public static string UrlEncode(this string input) {
+ internal static string UrlEncode(this string input) {
const int maxLength = 32766;
- if (input == null)
- throw new ArgumentNullException(nameof(input));
+ if (input == null) throw new ArgumentNullException(nameof(input));
- if (input.Length <= maxLength)
- return Uri.EscapeDataString(input);
+ if (input.Length <= maxLength) return Uri.EscapeDataString(input);
var sb = new StringBuilder(input.Length * 2);
var index = 0;
@@ -67,37 +66,18 @@ public static string UrlEncode(this string input) {
return sb.ToString();
}
- public static string? UrlEncode(this string input, Encoding encoding) {
+ internal static string? UrlEncode(this string input, Encoding encoding) {
var encoded = HttpUtility.UrlEncode(input, encoding);
return encoded?.Replace("+", "%20");
}
- ///
- /// Remove underscores from a string
- ///
- /// String to process
- /// string
- public static string RemoveUnderscoresAndDashes(this string input) => input.Replace("_", "").Replace("-", "");
+ internal static string RemoveUnderscoresAndDashes(this string input) => input.Replace("_", "").Replace("-", "");
- ///
- /// Converts a string to pascal case
- ///
- /// String to convert
- ///
- /// string
- public static string ToPascalCase(this string lowercaseAndUnderscoredWord, CultureInfo culture)
+ internal static string ToPascalCase(this string lowercaseAndUnderscoredWord, CultureInfo culture)
=> ToPascalCase(lowercaseAndUnderscoredWord, true, culture);
- ///
- /// Converts a string to pascal case with the option to remove underscores
- ///
- /// String to convert
- /// Option to remove underscores
- ///
- ///
- public static string ToPascalCase(this string text, bool removeUnderscores, CultureInfo culture) {
- if (string.IsNullOrEmpty(text))
- return text;
+ internal static string ToPascalCase(this string text, bool removeUnderscores, CultureInfo culture) {
+ if (string.IsNullOrEmpty(text)) return text;
text = text.Replace('_', ' ');
@@ -113,69 +93,17 @@ string CaseWord(string word) {
var restOfWord = word.Substring(1);
var firstChar = char.ToUpper(word[0], culture);
- if (restOfWord.IsUpperCase())
- restOfWord = restOfWord.ToLower(culture);
+ if (restOfWord.IsUpperCase()) restOfWord = restOfWord.ToLower(culture);
return string.Concat(firstChar, restOfWord);
}
}
- ///
- /// Converts a string to camel case
- ///
- /// String to convert
- ///
- /// String
- public static string ToCamelCase(this string lowercaseAndUnderscoredWord, CultureInfo culture)
+ internal static string ToCamelCase(this string lowercaseAndUnderscoredWord, CultureInfo culture)
=> MakeInitialLowerCase(ToPascalCase(lowercaseAndUnderscoredWord, culture), culture);
- static string MakeInitialLowerCase(this string word, CultureInfo culture)
- => string.Concat(word.Substring(0, 1).ToLower(culture), word.Substring(1));
-
- static string AddUnderscores(this string pascalCasedWord)
- => AddUnderscoresRegex1.Replace(
- AddUnderscoresRegex2.Replace(
- AddUnderscoresRegex3.Replace(pascalCasedWord, "$1_$2"),
- "$1_$2"
- ),
- "_"
- );
-
- static string AddDashes(this string pascalCasedWord)
- => AddDashesRegex1.Replace(
- AddDashesRegex2.Replace(
- AddDashesRegex3.Replace(pascalCasedWord, "$1-$2"),
- "$1-$2"
- ),
- "-"
- );
-
- static bool IsUpperCase(this string inputString) => IsUpperCaseRegex.IsMatch(inputString);
-
- static string AddUnderscorePrefix(this string pascalCasedWord) => $"_{pascalCasedWord}";
-
- static string AddSpaces(this string pascalCasedWord)
- => AddSpacesRegex1.Replace(
- AddSpacesRegex2.Replace(
- AddSpacesRegex3.Replace(pascalCasedWord, "$1 $2"),
- "$1 $2"
- ),
- " "
- );
-
- internal static bool IsEmpty(this string? value) => string.IsNullOrWhiteSpace(value);
-
- internal static bool IsNotEmpty(this string? value) => !string.IsNullOrWhiteSpace(value);
-
- ///
- /// Return possible variants of a name for name matching.
- ///
- /// String to convert
- /// The culture to use for conversion
- /// IEnumerable<string>
- public static IEnumerable GetNameVariants(this string name, CultureInfo culture) {
- if (string.IsNullOrEmpty(name))
- yield break;
+ internal static IEnumerable GetNameVariants(this string name, CultureInfo culture) {
+ if (string.IsNullOrEmpty(name)) yield break;
yield return name;
@@ -216,8 +144,46 @@ public static IEnumerable GetNameVariants(this string name, CultureInfo
yield return name.AddSpaces().ToLower(culture);
}
+ internal static bool IsEmpty(this string? value) => string.IsNullOrWhiteSpace(value);
+
+ internal static bool IsNotEmpty(this string? value) => !string.IsNullOrWhiteSpace(value);
+
internal static string JoinToString(this IEnumerable collection, string separator, Func getString)
=> JoinToString(collection.Select(getString), separator);
internal static string JoinToString(this IEnumerable strings, string separator) => string.Join(separator, strings);
-}
\ No newline at end of file
+
+ static string MakeInitialLowerCase(this string word, CultureInfo culture)
+ => string.Concat(word.Substring(0, 1).ToLower(culture), word.Substring(1));
+
+ static string AddUnderscores(this string pascalCasedWord)
+ => AddUnderscoresRegex1.Replace(
+ AddUnderscoresRegex2.Replace(
+ AddUnderscoresRegex3.Replace(pascalCasedWord, "$1_$2"),
+ "$1_$2"
+ ),
+ "_"
+ );
+
+ static string AddDashes(this string pascalCasedWord)
+ => AddDashesRegex1.Replace(
+ AddDashesRegex2.Replace(
+ AddDashesRegex3.Replace(pascalCasedWord, "$1-$2"),
+ "$1-$2"
+ ),
+ "-"
+ );
+
+ static bool IsUpperCase(this string inputString) => IsUpperCaseRegex.IsMatch(inputString);
+
+ static string AddUnderscorePrefix(this string pascalCasedWord) => $"_{pascalCasedWord}";
+
+ static string AddSpaces(this string pascalCasedWord)
+ => AddSpacesRegex1.Replace(
+ AddSpacesRegex2.Replace(
+ AddSpacesRegex3.Replace(pascalCasedWord, "$1 $2"),
+ "$1 $2"
+ ),
+ " "
+ );
+}
diff --git a/src/RestSharp/IRestClient.cs b/src/RestSharp/IRestClient.cs
new file mode 100644
index 000000000..ab203db07
--- /dev/null
+++ b/src/RestSharp/IRestClient.cs
@@ -0,0 +1,45 @@
+// 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.Serializers;
+
+namespace RestSharp;
+
+public interface IRestClient : IDisposable {
+ ///
+ /// Client options that aren't used for configuring HttpClient
+ ///
+ ReadOnlyRestClientOptions Options { get; }
+
+ ///
+ /// Client-level serializers
+ ///
+ RestSerializers Serializers { get; }
+
+ ///
+ /// Executes the request asynchronously, authenticating if needed
+ ///
+ /// Request to be executed
+ /// Cancellation token
+ Task ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default);
+
+ ///
+ /// A specialized method to download files as streams.
+ ///
+ /// Pre-configured request instance.
+ ///
+ /// The downloaded stream.
+ Task DownloadStreamAsync(RestRequest request, CancellationToken cancellationToken = default);
+}
diff --git a/src/RestSharp/KnownHeaders.cs b/src/RestSharp/KnownHeaders.cs
index 7943f9eed..cf678c72a 100644
--- a/src/RestSharp/KnownHeaders.cs
+++ b/src/RestSharp/KnownHeaders.cs
@@ -30,10 +30,12 @@ public static class KnownHeaders {
public const string ContentLocation = "Content-Location";
public const string ContentRange = "Content-Range";
public const string ContentType = "Content-Type";
- public const string Cookie = "Cookie";
public const string LastModified = "Last-Modified";
public const string ContentMD5 = "Content-MD5";
public const string Host = "Host";
+ public const string Cookie = "Cookie";
+ public const string SetCookie = "Set-Cookie";
+ public const string UserAgent = "User-Agent";
internal static readonly string[] ContentHeaders = {
Allow, Expires, ContentDisposition, ContentEncoding, ContentLanguage, ContentLength, ContentLocation, ContentRange, ContentType, ContentMD5,
diff --git a/src/RestSharp/RestClientOptions.cs b/src/RestSharp/Options/RestClientOptions.cs
similarity index 50%
rename from src/RestSharp/RestClientOptions.cs
rename to src/RestSharp/Options/RestClientOptions.cs
index 83d820660..d5efd312e 100644
--- a/src/RestSharp/RestClientOptions.cs
+++ b/src/RestSharp/Options/RestClientOptions.cs
@@ -19,9 +19,12 @@
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using RestSharp.Authenticators;
+using RestSharp.Extensions;
namespace RestSharp;
+[GenerateImmutable]
public class RestClientOptions {
static readonly Version Version = new AssemblyName(typeof(RestClientOptions).Assembly.FullName!).Version!;
@@ -34,21 +37,37 @@ public RestClientOptions() { }
public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(baseUrl, nameof(baseUrl)))) { }
///
- /// Explicit Host header value to use in requests independent from the request URI.
- /// If null, default host value extracted from URI is used.
+ /// Base URL for all requests made with this client instance
///
public Uri? BaseUrl { get; set; }
+ ///
+ /// Custom configuration for the underlying
+ ///
public Func? ConfigureMessageHandler { get; set; }
///
- /// In general you would not need to set this directly. Used by the NtlmAuthenticator.
+ /// Function to calculate the response status. By default, the status will be Completed if it was successful, or NotFound.
+ ///
+ public CalculateResponseStatus CalculateResponseStatus { get; set; } = httpResponse
+ => httpResponse.IsSuccessStatusCode || httpResponse.StatusCode == HttpStatusCode.NotFound
+ ? ResponseStatus.Completed
+ : ResponseStatus.Error;
+
+ ///
+ /// Authenticator that will be used to populate request with necessary authentication data
+ ///
+ public IAuthenticator? Authenticator { get; set; }
+
+ ///
+ /// Passed to Credentials
property
///
public ICredentials? Credentials { get; set; }
///
/// Determine whether or not the "default credentials" (e.g. the user account under which the current process is
/// running) will be sent along to the server. The default is false.
+ /// Passed to UseDefaultCredentials
property
///
public bool UseDefaultCredentials { get; set; }
@@ -57,12 +76,18 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba
///
public bool DisableCharset { get; set; }
-#if NETSTANDARD
- public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.GZip;
-#else
+ ///
+ /// Set the decompression method to use when making requests
+ ///
+#if NET
public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.All;
+#else
+ public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.GZip;
#endif
+ ///
+ /// Set the maximum number of redirects to follow
+ ///
public int? MaxRedirects { get; set; }
///
@@ -70,62 +95,87 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba
///
public X509CertificateCollection? ClientCertificates { get; set; }
- public IWebProxy? Proxy { get; set; }
- public CacheControlHeaderValue? CachePolicy { get; set; }
- public bool FollowRedirects { get; set; } = true;
- public bool? Expect100Continue { get; set; } = null;
- public string? UserAgent { get; set; } = DefaultUserAgent;
+ ///
+ /// Set the proxy to use when making requests. Default is null, which will use the default system proxy if one is set.
+ ///
+ public IWebProxy? Proxy { get; set; }
///
- /// Maximum request duration in milliseconds. When the request timeout is specified using ,
- /// the lowest value between the client timeout and request timeout will be used.
+ /// Cache policy to be used for requests using
///
- public int MaxTimeout { get; set; }
+ public CacheControlHeaderValue? CachePolicy { get; set; }
- public Encoding Encoding { get; set; } = Encoding.UTF8;
+ ///
+ /// Instruct the client to follow redirects. Default is true.
+ ///
+ public bool FollowRedirects { get; set; } = true;
- [Obsolete("Use MaxTimeout instead")]
- public int Timeout {
- get => MaxTimeout;
- set => MaxTimeout = value;
- }
+ ///
+ /// Gets or sets a value that indicates if the header for an HTTP request contains Continue.
+ ///
+ public bool? Expect100Continue { get; set; } = null;
+
+ ///
+ /// Value of the User-Agent header to be sent with requests. Default is "RestSharp/{version}"
+ ///
+ public string? UserAgent { get; set; } = DefaultUserAgent;
///
- /// Flag to send authorisation header with the HttpWebRequest
+ /// Passed to property
///
public bool PreAuthenticate { get; set; }
///
- /// Modifies the default behavior of RestSharp to swallow exceptions.
- /// When set to true
, a will be thrown
- /// in case RestSharp fails to deserialize the response.
+ /// Callback function for handling the validation of remote certificates. Useful for certificate pinning and
+ /// overriding certificate errors in the scope of a request.
+ ///
+ public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; }
+
+ ///
+ /// Sets the value of the Host header to be sent with requests.
+ ///
+ public string? BaseHost { get; set; }
+
+ ///
+ /// Maximum request duration in milliseconds. When the request timeout is specified using ,
+ /// the lowest value between the client timeout and request timeout will be used.
+ ///
+ public int MaxTimeout { get; set; }
+
+ ///
+ /// Default encoding to use when no encoding is specified in the content type header.
+ ///
+ public Encoding Encoding { get; set; } = Encoding.UTF8;
+
+ ///
+ /// Set to true to throw an exception when a deserialization error occurs. Default is false.
///
public bool ThrowOnDeserializationError { get; set; }
///
- /// Modifies the default behavior of RestSharp to swallow exceptions.
- /// When set to true
, RestSharp will consider the request as unsuccessful
- /// in case it fails to deserialize the response.
+ /// When set to true, the response status will be set to
+ /// when a deserialization error occurs. Default is true.
///
public bool FailOnDeserializationError { get; set; } = true;
///
- /// Modifies the default behavior of RestSharp to swallow exceptions.
- /// When set to true
, exceptions will be re-thrown.
+ /// Set to true to throw an exception when throws an exception when making a request.
+ /// Default is false.
///
public bool ThrowOnAnyError { get; set; }
///
- /// Callback function for handling the validation of remote certificates. Useful for certificate pinning and
- /// overriding certificate errors in the scope of a request.
+ /// Set to true to allow multiple default parameters with the same name. Default is false.
///
- public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; }
+ public bool AllowMultipleDefaultParametersWithSameName { get; set; }
- public string? BaseHost { get; set; }
+ ///
+ /// Custom function to encode a string for use in a URL.
+ ///
+ public Func Encode { get; set; } = s => s.UrlEncode();
///
- /// By default, RestSharp doesn't allow multiple parameters to have the same name.
- /// This properly allows to override the default behavior.
+ /// Custom function to encode a string for use in a URL query.
///
- public bool AllowMultipleDefaultParametersWithSameName { get; set; }
+ public Func EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding)!;
}
diff --git a/src/RestSharp/Options/RestRequestOptions.cs b/src/RestSharp/Options/RestRequestOptions.cs
new file mode 100644
index 000000000..7dfdadc44
--- /dev/null
+++ b/src/RestSharp/Options/RestRequestOptions.cs
@@ -0,0 +1,30 @@
+// 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.Http.Headers;
+using RestSharp.Authenticators;
+
+namespace RestSharp;
+
+public class RestRequestOptions {
+ ///
+ /// Authenticator that will be used to populate request with necessary authentication data
+ ///
+ public IAuthenticator? Authenticator { get; set; }
+
+ public CacheControlHeaderValue? CachePolicy { get; set; }
+
+ public string? BaseHost { get; set; }
+}
diff --git a/src/RestSharp/Parameters/BodyParameter.cs b/src/RestSharp/Parameters/BodyParameter.cs
index 6df933a22..cc63fbfad 100644
--- a/src/RestSharp/Parameters/BodyParameter.cs
+++ b/src/RestSharp/Parameters/BodyParameter.cs
@@ -16,7 +16,7 @@
namespace RestSharp;
public record BodyParameter : Parameter {
- public BodyParameter(string? name, object value, string contentType, DataFormat dataFormat = DataFormat.None)
+ public BodyParameter(string? name, object value, ContentType contentType, DataFormat dataFormat = DataFormat.None)
: base(name, Ensure.NotNull(value, nameof(value)), ParameterType.RequestBody, false) {
if (dataFormat == DataFormat.Binary && value is not byte[]) {
throw new ArgumentException("Binary data format needs a byte array as value");
@@ -26,7 +26,7 @@ public BodyParameter(string? name, object value, string contentType, DataFormat
DataFormat = dataFormat;
}
- public BodyParameter(object value, string contentType, DataFormat dataFormat = DataFormat.None)
+ public BodyParameter(object value, ContentType contentType, DataFormat dataFormat = DataFormat.None)
: this("", value, contentType, dataFormat) { }
///
@@ -41,20 +41,20 @@ public BodyParameter(object value, string contentType, DataFormat dataFormat = D
}
public record XmlParameter : BodyParameter {
- public XmlParameter(string name, object value, string? xmlNamespace = null, string contentType = Serializers.ContentType.Xml)
- : base(name, value, contentType, DataFormat.Xml)
+ public XmlParameter(string name, object value, string? xmlNamespace = null, ContentType? contentType = null)
+ : base(name, value, contentType ?? ContentType.Xml, DataFormat.Xml)
=> XmlNamespace = xmlNamespace;
- public XmlParameter(object value, string? xmlNamespace = null, string contentType = Serializers.ContentType.Xml)
+ public XmlParameter(object value, string? xmlNamespace = null, ContentType? contentType = null)
: this("", value, xmlNamespace, contentType) { }
public string? XmlNamespace { get; }
}
public record JsonParameter : BodyParameter {
- public JsonParameter(string name, object value, string contentType = Serializers.ContentType.Json)
- : base(name, value, contentType, DataFormat.Json) { }
+ public JsonParameter(string name, object value, ContentType? contentType = null)
+ : base(name, value, contentType ?? ContentType.Json, DataFormat.Json) { }
- public JsonParameter(object value, string contentType = Serializers.ContentType.Json)
+ public JsonParameter(object value, ContentType? contentType = null)
: this("", value, contentType) { }
}
diff --git a/src/RestSharp/Parameters/FileParameter.cs b/src/RestSharp/Parameters/FileParameter.cs
index 25ff43a27..75b314aa0 100644
--- a/src/RestSharp/Parameters/FileParameter.cs
+++ b/src/RestSharp/Parameters/FileParameter.cs
@@ -32,7 +32,7 @@ public record FileParameter {
///
/// MIME content type of file
///
- public string? ContentType { get; }
+ public ContentType ContentType { get; }
///
/// Provides raw data for file
@@ -41,12 +41,12 @@ public record FileParameter {
public FileParameterOptions Options { get; }
- FileParameter(string name, string fileName, Func getFile, string? contentType, FileParameterOptions options) {
+ FileParameter(string name, string fileName, Func getFile, ContentType? contentType, FileParameterOptions options) {
Name = name;
FileName = fileName;
GetFile = getFile;
Options = options;
- ContentType = contentType ?? Serializers.ContentType.Binary;
+ ContentType = contentType ?? ContentType.Binary;
}
///
@@ -58,7 +58,13 @@ public record FileParameter {
/// The content type to use in the request.
/// File parameter options
/// The
- public static FileParameter Create(string name, byte[] data, string filename, string? contentType = null, FileParameterOptions? options = null) {
+ public static FileParameter Create(
+ string name,
+ byte[] data,
+ string filename,
+ ContentType? contentType = null,
+ FileParameterOptions? options = null
+ ) {
return new FileParameter(name, filename, GetFile, contentType, options ?? new FileParameterOptions());
Stream GetFile() {
@@ -83,12 +89,17 @@ public static FileParameter Create(
string name,
Func getFile,
string fileName,
- string? contentType = null,
+ ContentType? contentType = null,
FileParameterOptions? options = null
)
- => new(name, fileName, getFile, contentType ?? Serializers.ContentType.Binary, options ?? new FileParameterOptions());
+ => new(name, fileName, getFile, contentType, options ?? new FileParameterOptions());
- public static FileParameter FromFile(string fullPath, string? name = null, string? contentType = null, FileParameterOptions? options = null) {
+ public static FileParameter FromFile(
+ string fullPath,
+ string? name = null,
+ ContentType? contentType = null,
+ FileParameterOptions? options = null
+ ) {
if (!File.Exists(Ensure.NotEmptyString(fullPath, nameof(fullPath)))) throw new FileNotFoundException("File not found", fullPath);
var fileName = Path.GetFileName(fullPath);
diff --git a/src/RestSharp/Parameters/Parameter.cs b/src/RestSharp/Parameters/Parameter.cs
index 7eef993eb..f37cd9f2d 100644
--- a/src/RestSharp/Parameters/Parameter.cs
+++ b/src/RestSharp/Parameters/Parameter.cs
@@ -21,7 +21,7 @@ public abstract record Parameter(string? Name, object? Value, ParameterType Type
///
/// MIME content type of the parameter
///
- public string? ContentType { get; protected init; }
+ public ContentType ContentType { get; protected init; } = ContentType.Undefined;
///
/// Return a human-readable representation of this parameter
@@ -32,10 +32,10 @@ public abstract record Parameter(string? Name, object? Value, ParameterType Type
public static Parameter CreateParameter(string? name, object? value, ParameterType type, bool encode = true)
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
=> type switch {
- ParameterType.GetOrPost => new GetOrPostParameter(name!, value?.ToString(), encode),
- ParameterType.UrlSegment => new UrlSegmentParameter(name!, value?.ToString()!, encode),
+ ParameterType.GetOrPost => new GetOrPostParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString(), encode),
+ ParameterType.UrlSegment => new UrlSegmentParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString()!, encode),
ParameterType.HttpHeader => new HeaderParameter(name, value?.ToString()),
- ParameterType.QueryString => new QueryParameter(name!, value?.ToString(), encode),
+ ParameterType.QueryString => new QueryParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString(), encode),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
diff --git a/src/RestSharp/Parameters/ParametersCollection.cs b/src/RestSharp/Parameters/ParametersCollection.cs
index d1844264f..8423f7f93 100644
--- a/src/RestSharp/Parameters/ParametersCollection.cs
+++ b/src/RestSharp/Parameters/ParametersCollection.cs
@@ -49,21 +49,7 @@ public ParametersCollection AddParameters(ParametersCollection parameters) {
public ParametersCollection GetParameters(ParameterType parameterType) => new(_parameters.Where(x => x.Type == parameterType));
- public ParametersCollection GetParameters() => new(_parameters.Where(x => x is T));
-
- internal ParametersCollection GetQueryParameters(Method method) {
- Func condition =
- !IsPost(method)
- ? p => p.Type is ParameterType.GetOrPost or ParameterType.QueryString
- : p => p.Type is ParameterType.QueryString;
-
- return new ParametersCollection(_parameters.Where(p => condition(p)));
- }
-
- internal ParametersCollection? GetContentParameters(Method method)
- => IsPost(method) ? new ParametersCollection(GetParameters()) : null;
-
- static bool IsPost(Method method) => method is Method.Post or Method.Put or Method.Patch;
+ public IEnumerable GetParameters() where T : class => _parameters.OfType();
public IEnumerator GetEnumerator() => _parameters.GetEnumerator();
diff --git a/src/RestSharp/Parameters/ParametersCollectionExtensions.cs b/src/RestSharp/Parameters/ParametersCollectionExtensions.cs
new file mode 100644
index 000000000..b003b326d
--- /dev/null
+++ b/src/RestSharp/Parameters/ParametersCollectionExtensions.cs
@@ -0,0 +1,32 @@
+// 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;
+
+static class ParametersCollectionExtensions {
+ internal static IEnumerable GetQueryParameters(this ParametersCollection parameters, Method method) {
+ Func condition =
+ !IsPost(method)
+ ? p => p.Type is ParameterType.GetOrPost or ParameterType.QueryString
+ : p => p.Type is ParameterType.QueryString;
+
+ return parameters.Where(p => condition(p));
+ }
+
+ internal static IEnumerable GetContentParameters(this ParametersCollection parameters, Method method)
+ => IsPost(method) ? parameters.GetParameters() : Enumerable.Empty();
+
+ static bool IsPost(Method method) => method is Method.Post or Method.Put or Method.Patch;
+}
diff --git a/src/RestSharp/Parameters/UrlSegmentParameter.cs b/src/RestSharp/Parameters/UrlSegmentParameter.cs
index 04b60d65c..deb0b3bdc 100644
--- a/src/RestSharp/Parameters/UrlSegmentParameter.cs
+++ b/src/RestSharp/Parameters/UrlSegmentParameter.cs
@@ -16,7 +16,7 @@ namespace RestSharp;
public record UrlSegmentParameter : NamedParameter {
///
- /// Instantiates a new query parameter instance that will be added to the request URL part of the query string.
+ /// Instantiates a new query parameter instance that will be added to the request URL by replacing part of the absolute path.
/// The request resource should have a placeholder {name} that will be replaced with the parameter value when the request is made.
///
/// Parameter name
diff --git a/src/RestSharp/Properties/AssemblyInfo.cs b/src/RestSharp/Properties/AssemblyInfo.cs
index 08b227485..77ebd40f7 100644
--- a/src/RestSharp/Properties/AssemblyInfo.cs
+++ b/src/RestSharp/Properties/AssemblyInfo.cs
@@ -1,6 +1,9 @@
using System.Runtime.CompilerServices;
[assembly:
+ InternalsVisibleTo(
+ "RestSharp.InteractiveTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fda57af14a288d46e3efea89617037585c4de57159cd536ca6dff792ea1d6addc665f2fccb4285413d9d44db5a1be87cb82686db200d16325ed9c42c89cd4824d8cc447f7cee2ac000924c3bceeb1b7fcb5cc1a3901785964d48ce14172001084134f4dcd9973c3776713b595443b1064bb53e2eeb924969244d354e46495e9d"
+ ),
InternalsVisibleTo(
"RestSharp.Tests.Integrated, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fda57af14a288d46e3efea89617037585c4de57159cd536ca6dff792ea1d6addc665f2fccb4285413d9d44db5a1be87cb82686db200d16325ed9c42c89cd4824d8cc447f7cee2ac000924c3bceeb1b7fcb5cc1a3901785964d48ce14172001084134f4dcd9973c3776713b595443b1064bb53e2eeb924969244d354e46495e9d"
),
@@ -10,4 +13,4 @@
InternalsVisibleTo(
"RestSharp.Serializers.Xml, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fda57af14a288d46e3efea89617037585c4de57159cd536ca6dff792ea1d6addc665f2fccb4285413d9d44db5a1be87cb82686db200d16325ed9c42c89cd4824d8cc447f7cee2ac000924c3bceeb1b7fcb5cc1a3901785964d48ce14172001084134f4dcd9973c3776713b595443b1064bb53e2eeb924969244d354e46495e9d"
)]
-[assembly: CLSCompliant(true)]
\ No newline at end of file
+[assembly: CLSCompliant(true)]
diff --git a/src/RestSharp/Properties/IsExternalInit.cs b/src/RestSharp/Properties/IsExternalInit.cs
index deb2fb270..4f3c65f81 100644
--- a/src/RestSharp/Properties/IsExternalInit.cs
+++ b/src/RestSharp/Properties/IsExternalInit.cs
@@ -1,4 +1,4 @@
-#if NETSTANDARD
+#if NETSTANDARD || NETFRAMEWORK
using System.ComponentModel;
// ReSharper disable once CheckNamespace
diff --git a/src/RestSharp/Request/BodyExtensions.cs b/src/RestSharp/Request/BodyExtensions.cs
index 20c07aa52..55b5c4de9 100644
--- a/src/RestSharp/Request/BodyExtensions.cs
+++ b/src/RestSharp/Request/BodyExtensions.cs
@@ -15,6 +15,8 @@
namespace RestSharp;
+using System.Diagnostics.CodeAnalysis;
+
static class BodyExtensions {
public static bool TryGetBodyParameter(this RestRequest request, out BodyParameter? bodyParameter) {
bodyParameter = request.Parameters.FirstOrDefault(p => p.Type == ParameterType.RequestBody) as BodyParameter;
@@ -23,5 +25,5 @@ public static bool TryGetBodyParameter(this RestRequest request, out BodyParamet
public static bool HasFiles(this RestRequest request) => request.Files.Count > 0;
- public static bool IsEmpty(this ParametersCollection? parameters) => parameters == null || parameters.Count == 0;
-}
\ No newline at end of file
+ public static bool IsEmpty([NotNullWhen(false)]this ParametersCollection? parameters) => parameters == null || parameters.Count == 0;
+}
diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs
index fe2facf12..bf900be82 100644
--- a/src/RestSharp/Request/RequestContent.cs
+++ b/src/RestSharp/Request/RequestContent.cs
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using System.Net;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using RestSharp.Extensions;
@@ -37,8 +36,8 @@ public RequestContent(RestClient client, RestRequest request) {
public HttpContent BuildContent() {
AddFiles();
- var postParameters = _request.Parameters.GetContentParameters(_request.Method);
- AddBody(!postParameters.IsEmpty());
+ var postParameters = _request.Parameters.GetContentParameters(_request.Method).ToArray();
+ AddBody(postParameters.Length > 0);
AddPostParameters(postParameters);
AddHeaders();
@@ -55,7 +54,7 @@ void AddFiles() {
_streams.Add(stream);
var fileContent = new StreamContent(stream);
- if (file.ContentType != null) fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(file.ContentType);
+ fileContent.Headers.ContentType = file.ContentType.AsMediaTypeHeaderValue;
var dispositionHeader = file.Options.DisableFilenameEncoding
? ContentDispositionHeaderValue.Parse($"form-data; name=\"{file.Name}\"; filename=\"{file.FileName}\"")
@@ -74,14 +73,18 @@ void AddFiles() {
HttpContent Serialize(BodyParameter body) {
return body.DataFormat switch {
- DataFormat.None => new StringContent(body.Value!.ToString()!, _client.Options.Encoding, body.ContentType),
+ DataFormat.None => new StringContent(
+ body.Value!.ToString()!,
+ _client.Options.Encoding,
+ body.ContentType.Value
+ ),
DataFormat.Binary => GetBinary(),
_ => GetSerialized()
};
HttpContent GetBinary() {
var byteContent = new ByteArrayContent((body.Value as byte[])!);
- byteContent.Headers.ContentType = MediaTypeHeaderValue.Parse(body.ContentType);
+ byteContent.Headers.ContentType = body.ContentType.AsMediaTypeHeaderValue;
if (body.ContentEncoding != null) {
byteContent.Headers.ContentEncoding.Clear();
@@ -92,27 +95,23 @@ HttpContent GetBinary() {
}
HttpContent GetSerialized() {
- if (!_client.Serializers.TryGetValue(body.DataFormat, out var serializerRecord))
- throw new InvalidDataContractException(
- $"Can't find serializer for content type {body.DataFormat}"
- );
-
- var serializer = serializerRecord.GetSerializer();
-
- var content = serializer.Serialize(body);
+ var serializer = _client.Serializers.GetSerializer(body.DataFormat);
+ var content = serializer.Serialize(body);
if (content == null) throw new SerializationException("Request body serialized to null");
+ var contentType = body.ContentType.Or(serializer.Serializer.ContentType);
+
return new StringContent(
content,
_client.Options.Encoding,
- body.ContentType ?? serializer.Serializer.ContentType
+ contentType.Value
);
}
}
static bool BodyShouldBeMultipartForm(BodyParameter bodyParameter) {
- var bodyContentType = bodyParameter.ContentType ?? bodyParameter.Name;
+ var bodyContentType = bodyParameter.ContentType.OrValue(bodyParameter.Name);
return bodyParameter.Name.IsNotEmpty() && bodyParameter.Name != bodyContentType;
}
@@ -146,38 +145,33 @@ void AddBody(bool hasPostParameters) {
}
}
- void AddPostParameters(ParametersCollection? postParameters) {
- if (postParameters.IsEmpty()) return;
+ void AddPostParameters(GetOrPostParameter[] postParameters) {
+ if (postParameters.Length == 0) return;
if (Content is MultipartFormDataContent mpContent) {
// we got the multipart form already instantiated, just add parameters to it
- foreach (var postParameter in postParameters!) {
+ foreach (var postParameter in postParameters) {
var parameterName = postParameter.Name!;
mpContent.Add(
- new StringContent(postParameter.Value!.ToString()!, _client.Options.Encoding, postParameter.ContentType),
+ new StringContent(postParameter.Value?.ToString() ?? "", _client.Options.Encoding, postParameter.ContentType.Value),
_request.MultipartFormQuoteParameters ? $"\"{parameterName}\"" : parameterName
);
}
}
else {
-#if NETCORE
+#if NET
// We should not have anything else except the parameters, so we send them as form URL encoded.
var formContent = new FormUrlEncodedContent(
- _request.Parameters
- .Where(x => x.Type == ParameterType.GetOrPost)
- .Select(x => new KeyValuePair(x.Name!, x.Value!.ToString()!))!
+ postParameters
+ .Select(x => new KeyValuePair(x.Name!, x.Value?.ToString() ?? string.Empty))!
);
Content = formContent;
#else
// However due to bugs in HttpClient FormUrlEncodedContent (see https://github.com/restsharp/RestSharp/issues/1814) we
// do the encoding ourselves using WebUtility.UrlEncode instead.
- var formData = _request.Parameters
- .Where(x => x.Type == ParameterType.GetOrPost)
- .Select(x => new KeyValuePair(x.Name!, x.Value!.ToString()!))!;
- var encodedItems = formData.Select(i => $"{WebUtility.UrlEncode(i.Key)}={WebUtility.UrlEncode(i.Value)}" /*.Replace("%20", "+")*/);
- var encodedContent = new StringContent(string.Join("&", encodedItems), null, "application/x-www-form-urlencoded");
-
+ var encodedItems = postParameters.Select(x => $"{x.Name!.UrlEncode()}={x.Value?.ToString()?.UrlEncode() ?? string.Empty}");
+ var encodedContent = new StringContent(encodedItems.JoinToString("&"), null, ContentType.FormUrlEncoded.Value);
Content = encodedContent;
#endif
}
@@ -185,7 +179,8 @@ void AddPostParameters(ParametersCollection? postParameters) {
void AddHeaders() {
var contentHeaders = _request.Parameters
- .Where(x => x.Type == ParameterType.HttpHeader && IsContentHeader(x.Name!))
+ .GetParameters()
+ .Where(x => IsContentHeader(x.Name!))
.ToArray();
if (contentHeaders.Length > 0 && Content == null) {
@@ -195,12 +190,12 @@ void AddHeaders() {
contentHeaders.ForEach(AddHeader);
- void AddHeader(Parameter parameter) {
+ void AddHeader(HeaderParameter parameter) {
var parameterStringValue = parameter.Value!.ToString();
var value = parameter.Name switch {
- ContentType => GetContentTypeHeader(Ensure.NotNull(parameterStringValue, nameof(parameter))),
- _ => parameterStringValue
+ KnownHeaders.ContentType => GetContentTypeHeader(Ensure.NotNull(parameterStringValue, nameof(parameter))),
+ _ => parameterStringValue
};
var pName = Ensure.NotNull(parameter.Name, nameof(parameter.Name));
ReplaceHeader(pName, value);
diff --git a/src/RestSharp/Request/RequestHeaders.cs b/src/RestSharp/Request/RequestHeaders.cs
index d15b721b4..cec79961e 100644
--- a/src/RestSharp/Request/RequestHeaders.cs
+++ b/src/RestSharp/Request/RequestHeaders.cs
@@ -16,6 +16,7 @@
// ReSharper disable InvertIf
using System.Net;
+using RestSharp.Extensions;
namespace RestSharp;
@@ -30,7 +31,7 @@ public RequestHeaders AddHeaders(ParametersCollection parameters) {
// Add Accept header based on registered deserializers if none has been set by the caller.
public RequestHeaders AddAcceptHeader(string[] acceptedContentTypes) {
if (Parameters.TryFind(KnownHeaders.Accept) == null) {
- var accepts = string.Join(", ", acceptedContentTypes);
+ var accepts = acceptedContentTypes.JoinToString(", ");
Parameters.AddParameter(new HeaderParameter(KnownHeaders.Accept, accepts));
}
@@ -40,9 +41,11 @@ public RequestHeaders AddAcceptHeader(string[] acceptedContentTypes) {
// Add Cookie header from the cookie container
public RequestHeaders AddCookieHeaders(CookieContainer cookieContainer, Uri uri) {
var cookies = cookieContainer.GetCookieHeader(uri);
+
if (cookies.Length > 0) {
Parameters.AddParameter(new HeaderParameter(KnownHeaders.Cookie, cookies));
}
+
return this;
}
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs
index 4d81ac9ba..0cc33ba16 100644
--- a/src/RestSharp/Request/RestRequest.cs
+++ b/src/RestSharp/Request/RestRequest.cs
@@ -13,7 +13,10 @@
// limitations under the License.
using System.Net;
+using RestSharp.Authenticators;
using RestSharp.Extensions;
+
+// ReSharper disable ReplaceSubstringWithRangeIndexer
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace RestSharp;
@@ -22,8 +25,8 @@ namespace RestSharp;
/// Container for data used to make requests
///
public class RestRequest {
- readonly Func? _advancedResponseHandler;
- readonly Func? _responseWriter;
+ readonly Func? _advancedResponseHandler;
+ readonly Func? _responseWriter;
///
/// Default constructor
@@ -47,19 +50,18 @@ public RestRequest(string? resource, Method method = Method.Get) : this() {
var queryParams = ParseQuery(Resource.Substring(queryStringStart + 1));
Resource = Resource.Substring(0, queryStringStart);
- foreach (var param in queryParams)
- this.AddQueryParameter(param.Key, param.Value, false);
+ foreach (var param in queryParams) this.AddQueryParameter(param.Key, param.Value, false);
}
- static IEnumerable> ParseQuery(string query)
+ static IEnumerable> ParseQuery(string query)
=> query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
.Select(
x => {
var position = x.IndexOf('=');
return position > 0
- ? new KeyValuePair(x.Substring(0, position), x.Substring(position + 1))
- : new KeyValuePair(x, string.Empty);
+ ? new KeyValuePair(x.Substring(0, position), x.Substring(position + 1))
+ : new KeyValuePair(x, null);
}
);
}
@@ -78,14 +80,14 @@ public RestRequest(Uri resource, Method method = Method.Get)
/// Always send a multipart/form-data request - even when no Files are present.
///
public bool AlwaysMultipartFormData { get; set; }
-
+
///
/// When set to true, parameters in a multipart form data requests will be enclosed in
/// quotation marks. Default is false. Enable it if the remote endpoint requires parameters
/// to be in quotes (for example, FreshDesk API).
///
public bool MultipartFormQuoteParameters { get; set; }
-
+
public string? FormBoundary { get; set; }
///
@@ -99,6 +101,11 @@ public RestRequest(Uri resource, Method method = Method.Get)
///
public CookieContainer? CookieContainer { get; set; }
+ ///
+ /// Request-level authenticator. It will be used if set, otherwise RestClient.Authenticator will be used.
+ ///
+ public IAuthenticator? Authenticator { get; set; }
+
///
/// Container of all the files to be uploaded with the request.
///
@@ -188,11 +195,10 @@ public RestRequest(Uri resource, Method method = Method.Get)
///
/// Set this to handle the response stream yourself, based on the response details
///
- public Func? AdvancedResponseWriter {
+ public Func? AdvancedResponseWriter {
get => _advancedResponseHandler;
init {
- if (ResponseWriter != null)
- throw new ArgumentException("ResponseWriter is not null. Only one response writer can be used.");
+ if (ResponseWriter != null) throw new ArgumentException("ResponseWriter is not null. Only one response writer can be used.");
_advancedResponseHandler = value;
}
@@ -215,4 +221,4 @@ public RestRequest RemoveParameter(Parameter parameter) {
}
internal RestRequest AddFile(FileParameter file) => this.With(x => x._files.Add(file));
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs
index 60466f8cd..15f34dd49 100644
--- a/src/RestSharp/Request/RestRequestExtensions.cs
+++ b/src/RestSharp/Request/RestRequestExtensions.cs
@@ -14,8 +14,6 @@
using System.Net;
using System.Text.RegularExpressions;
-using RestSharp.Extensions;
-using RestSharp.Serializers;
namespace RestSharp;
@@ -262,35 +260,6 @@ public static RestRequest AddOrUpdateParameters(this RestRequest request, IEnume
return request;
}
- // TODO: Three methods below added for binary compatibility with v108. Remove for the next major release.
- // In addition, both contentType and options parameters should get default values.
-
- public static RestRequest AddFile(
- this RestRequest request,
- string name,
- string path,
- string? contentType = null
- )
- => request.AddFile(FileParameter.FromFile(path, name, contentType));
-
- public static RestRequest AddFile(
- this RestRequest request,
- string name,
- byte[] bytes,
- string fileName,
- string? contentType = null
- )
- => request.AddFile(FileParameter.Create(name, bytes, fileName, contentType));
-
- public static RestRequest AddFile(
- this RestRequest request,
- string name,
- Func getFile,
- string fileName,
- string? contentType = null
- )
- => request.AddFile(FileParameter.Create(name, getFile, fileName, contentType));
-
///
/// Adds a file parameter to the request body. The file will be read from disk as a stream.
///
@@ -304,8 +273,8 @@ public static RestRequest AddFile(
this RestRequest request,
string name,
string path,
- string? contentType,
- FileParameterOptions? options
+ ContentType? contentType = null,
+ FileParameterOptions? options = null
)
=> request.AddFile(FileParameter.FromFile(path, name, contentType, options));
@@ -324,8 +293,8 @@ public static RestRequest AddFile(
string name,
byte[] bytes,
string fileName,
- string? contentType,
- FileParameterOptions? options
+ ContentType? contentType = null,
+ FileParameterOptions? options = null
)
=> request.AddFile(FileParameter.Create(name, bytes, fileName, contentType, options));
@@ -344,8 +313,8 @@ public static RestRequest AddFile(
string name,
Func getFile,
string fileName,
- string? contentType,
- FileParameterOptions? options
+ ContentType? contentType = null,
+ FileParameterOptions? options = null
)
=> request.AddFile(FileParameter.Create(name, getFile, fileName, contentType, options));
@@ -358,22 +327,22 @@ public static RestRequest AddFile(
///
/// Thrown if request body type cannot be resolved
/// This method will try to figure out the right content type based on the request data format and the provided content type
- public static RestRequest AddBody(this RestRequest request, object obj, string? contentType = null) {
+ public static RestRequest AddBody(this RestRequest request, object obj, ContentType? contentType = null) {
if (contentType == null) {
return request.RequestFormat switch {
- DataFormat.Json => request.AddJsonBody(obj),
- DataFormat.Xml => request.AddXmlBody(obj),
+ DataFormat.Json => request.AddJsonBody(obj, contentType),
+ DataFormat.Xml => request.AddXmlBody(obj, contentType),
DataFormat.Binary => request.AddParameter(new BodyParameter("", obj, ContentType.Binary)),
_ => request.AddParameter(new BodyParameter("", obj.ToString()!, ContentType.Plain))
};
}
return
- obj is string str ? request.AddStringBody(str, contentType) :
- obj is byte[] bytes ? request.AddParameter(new BodyParameter("", bytes, contentType, DataFormat.Binary)) :
- contentType.Contains("xml") ? request.AddXmlBody(obj, contentType) :
- contentType.Contains("json") ? request.AddJsonBody(obj, contentType) :
- throw new ArgumentException("Non-string body found with unsupported content type", nameof(obj));
+ obj is string str ? request.AddStringBody(str, contentType) :
+ obj is byte[] bytes ? request.AddParameter(new BodyParameter("", bytes, contentType, DataFormat.Binary)) :
+ contentType.Value.Contains("xml") ? request.AddXmlBody(obj, contentType) :
+ contentType.Value.Contains("json") ? request.AddJsonBody(obj, contentType) :
+ throw new ArgumentException("Non-string body found with unsupported content type", nameof(obj));
}
///
@@ -385,7 +354,7 @@ public static RestRequest AddBody(this RestRequest request, object obj, string?
/// for the content
///
public static RestRequest AddStringBody(this RestRequest request, string body, DataFormat dataFormat) {
- var contentType = ContentType.FromDataFormat[dataFormat];
+ var contentType = ContentType.FromDataFormat(dataFormat);
request.RequestFormat = dataFormat;
return request.AddParameter(new BodyParameter("", body, contentType));
}
@@ -397,8 +366,8 @@ public static RestRequest AddStringBody(this RestRequest request, string body, D
/// String body
/// Content type of the body
///
- public static RestRequest AddStringBody(this RestRequest request, string body, string contentType)
- => request.AddParameter(new BodyParameter(body, Ensure.NotEmpty(contentType, nameof(contentType))));
+ public static RestRequest AddStringBody(this RestRequest request, string body, ContentType contentType)
+ => request.AddParameter(new BodyParameter(body, Ensure.NotNull(contentType, nameof(contentType))));
///
/// Adds a JSON body parameter to the request
@@ -407,7 +376,7 @@ public static RestRequest AddStringBody(this RestRequest request, string body, s
/// Object that will be serialized to JSON
/// Optional: content type. Default is "application/json"
///
- public static RestRequest AddJsonBody(this RestRequest request, T obj, string contentType = ContentType.Json) where T : class {
+ public static RestRequest AddJsonBody(this RestRequest request, T obj, ContentType? contentType = null) where T : class {
request.RequestFormat = DataFormat.Json;
return obj is string str ? request.AddStringBody(str, DataFormat.Json) : request.AddParameter(new JsonParameter(obj, contentType));
}
@@ -420,7 +389,7 @@ public static RestRequest AddJsonBody(this RestRequest request, T obj, string
/// Optional: content type. Default is "application/xml"
/// Optional: XML namespace
///
- public static RestRequest AddXmlBody(this RestRequest request, T obj, string contentType = ContentType.Xml, string xmlNamespace = "")
+ public static RestRequest AddXmlBody(this RestRequest request, T obj, ContentType? contentType = null, string xmlNamespace = "")
where T : class {
request.RequestFormat = DataFormat.Xml;
diff --git a/src/RestSharp/Request/UriExtensions.cs b/src/RestSharp/Request/UriExtensions.cs
index e56a267da..818ddbda4 100644
--- a/src/RestSharp/Request/UriExtensions.cs
+++ b/src/RestSharp/Request/UriExtensions.cs
@@ -47,20 +47,21 @@ params ParametersCollection[] parametersCollections
if (parameters.Count == 0) return mergedUri;
var uri = mergedUri.AbsoluteUri;
- var separator = uri.Contains("?") ? "&" : "?";
+ var separator = uri.Contains('?') ? "&" : "?";
return new Uri(string.Concat(uri, separator, EncodeParameters()));
string EncodeParameters() => string.Join("&", parameters.Select(EncodeParameter).ToArray());
- string EncodeParameter(Parameter parameter) {
- return
- !parameter.Encode
- ? $"{parameter.Name}={StringOrEmpty(parameter.Value)}"
- : $"{encodeQuery(parameter.Name!, encoding)}={encodeQuery(StringOrEmpty(parameter.Value), encoding)}";
-
- static string StringOrEmpty(object? value) => value == null ? "" : value.ToString()!;
+ string GetString(string name, string? value, Func? encode) {
+ var val = encode != null && value != null ? encode(value) : value;
+ return val == null ? name : $"{name}={val}";
}
+
+ string EncodeParameter(Parameter parameter)
+ => !parameter.Encode
+ ? GetString(parameter.Name!, parameter.Value?.ToString(), null)
+ : GetString(encodeQuery(parameter.Name!, encoding), parameter.Value?.ToString(), x => encodeQuery(x, encoding));
}
public static UrlSegmentParamsValues GetUrlSegmentParamsValues(
@@ -91,4 +92,4 @@ params ParametersCollection[] parametersCollections
}
}
-record UrlSegmentParamsValues(Uri Uri, string Resource);
\ No newline at end of file
+record UrlSegmentParamsValues(Uri Uri, string Resource);
diff --git a/src/RestSharp/Response/ResponseHandling.cs b/src/RestSharp/Response/ResponseHandling.cs
index a22fa05c0..43e0f4c56 100644
--- a/src/RestSharp/Response/ResponseHandling.cs
+++ b/src/RestSharp/Response/ResponseHandling.cs
@@ -36,7 +36,7 @@ Encoding TryGetEncoding(string es) {
}
public static Task ReadResponse(this HttpResponseMessage response, CancellationToken cancellationToken) {
-#if NETSTANDARD
+#if NETSTANDARD || NETFRAMEWORK
return response.Content.ReadAsStreamAsync();
# else
return response.Content.ReadAsStreamAsync(cancellationToken)!;
diff --git a/src/RestSharp/Response/ResponseThrowExtension.cs b/src/RestSharp/Response/ResponseThrowExtension.cs
new file mode 100644
index 000000000..7c4e99aa9
--- /dev/null
+++ b/src/RestSharp/Response/ResponseThrowExtension.cs
@@ -0,0 +1,32 @@
+// 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 class ResponseThrowExtension {
+ public static RestResponse ThrowIfError(this RestResponse response) {
+ var exception = response.GetException();
+ if (exception != null) throw exception;
+
+ return response;
+ }
+
+ public static RestResponse ThrowIfError(this RestResponse response) {
+ var exception = response.GetException();
+ if (exception != null) throw exception;
+
+ return response;
+ }
+}
diff --git a/src/RestSharp/Response/RestResponse.cs b/src/RestSharp/Response/RestResponse.cs
index 289a7a609..5f07e38d9 100644
--- a/src/RestSharp/Response/RestResponse.cs
+++ b/src/RestSharp/Response/RestResponse.cs
@@ -33,7 +33,7 @@ public class RestResponse : RestResponse {
public T? Data { get; set; }
public static RestResponse FromResponse(RestResponse response)
- => new() {
+ => new(response.Request) {
Content = response.Content,
RawBytes = response.RawBytes,
ContentEncoding = response.ContentEncoding,
@@ -50,15 +50,16 @@ public static RestResponse FromResponse(RestResponse response)
Server = response.Server,
StatusCode = response.StatusCode,
StatusDescription = response.StatusDescription,
- Request = response.Request,
RootElement = response.RootElement
};
+
+ public RestResponse(RestRequest request) : base(request) { }
}
///
/// Container for data sent back from API
///
-[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "()}")]
+[DebuggerDisplay($"{{{nameof(DebuggerDisplay)}()}}")]
public class RestResponse : RestResponseBase {
internal static async Task FromHttpResponse(
HttpResponseMessage httpResponse,
@@ -68,11 +69,11 @@ internal static async Task FromHttpResponse(
CalculateResponseStatus calculateResponseStatus,
CancellationToken cancellationToken
) {
- return request.AdvancedResponseWriter?.Invoke(httpResponse) ?? await GetDefaultResponse().ConfigureAwait(false);
+ return request.AdvancedResponseWriter?.Invoke(httpResponse, request) ?? await GetDefaultResponse().ConfigureAwait(false);
async Task GetDefaultResponse() {
var readTask = request.ResponseWriter == null ? ReadResponse() : ReadAndConvertResponse();
-#if NETSTANDARD
+#if NETSTANDARD || NETFRAMEWORK
using var stream = await readTask.ConfigureAwait(false);
#else
await using var stream = await readTask.ConfigureAwait(false);
@@ -81,7 +82,7 @@ async Task GetDefaultResponse() {
var bytes = request.ResponseWriter != null || stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
var content = bytes == null ? null : httpResponse.GetResponseString(bytes, encoding);
- return new RestResponse {
+ return new RestResponse(request) {
Content = content,
RawBytes = bytes,
ContentEncoding = httpResponse.Content.Headers.ContentEncoding,
@@ -95,7 +96,6 @@ async Task GetDefaultResponse() {
StatusCode = httpResponse.StatusCode,
StatusDescription = httpResponse.ReasonPhrase,
IsSuccessStatusCode = httpResponse.IsSuccessStatusCode,
- Request = request,
Headers = httpResponse.Headers.GetHeaderParameters(),
ContentHeaders = httpResponse.Content.Headers.GetHeaderParameters(),
Cookies = cookieCollection,
@@ -105,7 +105,7 @@ async Task GetDefaultResponse() {
Task ReadResponse() => httpResponse.ReadResponse(cancellationToken);
async Task ReadAndConvertResponse() {
-#if NETSTANDARD
+#if NETSTANDARD || NETFRAMEWORK
using var original = await ReadResponse().ConfigureAwait(false);
#else
await using var original = await ReadResponse().ConfigureAwait(false);
@@ -114,6 +114,10 @@ async Task GetDefaultResponse() {
}
}
}
+
+ public RestResponse(RestRequest request) : base(request) { }
+
+ public RestResponse() : base(new RestRequest()) { }
}
public delegate ResponseStatus CalculateResponseStatus(HttpResponseMessage httpResponse);
diff --git a/src/RestSharp/Response/RestResponseBase.cs b/src/RestSharp/Response/RestResponseBase.cs
index 498d3a7cf..0e5077950 100644
--- a/src/RestSharp/Response/RestResponseBase.cs
+++ b/src/RestSharp/Response/RestResponseBase.cs
@@ -25,7 +25,11 @@ public abstract class RestResponseBase {
///
/// Default constructor
///
- protected RestResponseBase() => ResponseStatus = ResponseStatus.None;
+ protected RestResponseBase(RestRequest request) {
+ ResponseStatus = ResponseStatus.None;
+ Request = request;
+ Request.IncreaseNumAttempts();
+ }
///
/// The RestRequest that was made to get this RestResponse
@@ -33,7 +37,7 @@ public abstract class RestResponseBase {
///
/// Mainly for debugging if ResponseStatus is not OK
///
- public RestRequest? Request { get; set; }
+ public RestRequest Request { get; set; }
///
/// MIME content type of response
@@ -125,7 +129,7 @@ public abstract class RestResponseBase {
/// HTTP protocol version of the request
///
public Version? Version { get; set; }
-
+
///
/// Root element of the serialized response content, only works if deserializer supports it
///
@@ -146,4 +150,9 @@ public abstract class RestResponseBase {
ResponseStatus.Completed => null,
_ => throw ErrorException ?? new ArgumentOutOfRangeException(nameof(ResponseStatus))
};
-}
\ No newline at end of file
+
+ internal void AddException(Exception exception) {
+ ErrorException = exception;
+ ErrorMessage = exception.Message;
+ }
+}
diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs
index 1d4d0dde8..0848f8442 100644
--- a/src/RestSharp/RestClient.Async.cs
+++ b/src/RestSharp/RestClient.Async.cs
@@ -18,35 +18,67 @@
namespace RestSharp;
public partial class RestClient {
- ///
- /// Executes the request asynchronously, authenticating if needed
- ///
- /// Request to be executed
- /// Cancellation token
+ ///
public async Task ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default) {
- var internalResponse = await ExecuteInternal(request, cancellationToken).ConfigureAwait(false);
+ var internalResponse = await ExecuteRequestAsync(request, cancellationToken).ConfigureAwait(false);
- var response = new RestResponse();
-
- response = internalResponse.Exception == null
+ var response = internalResponse.Exception == null
? await RestResponse.FromHttpResponse(
internalResponse.ResponseMessage!,
request,
Options.Encoding,
- request.CookieContainer!.GetCookies(internalResponse.Url),
- CalculateResponseStatus,
+ internalResponse.CookieContainer?.GetCookies(internalResponse.Url),
+ Options.CalculateResponseStatus,
cancellationToken
)
.ConfigureAwait(false)
- : AddError(response, internalResponse.Exception, internalResponse.TimeoutToken);
-
- response.Request = request;
- response.Request.IncreaseNumAttempts();
+ : GetErrorResponse(request, internalResponse.Exception, internalResponse.TimeoutToken);
return Options.ThrowOnAnyError ? response.ThrowIfError() : response;
}
- async Task ExecuteInternal(RestRequest request, CancellationToken cancellationToken) {
+ ///
+ [PublicAPI]
+ public async Task DownloadStreamAsync(RestRequest request, CancellationToken cancellationToken = default) {
+ // Make sure we only read the headers so we can stream the content body efficiently
+ request.CompletionOption = HttpCompletionOption.ResponseHeadersRead;
+ var response = await ExecuteRequestAsync(request, cancellationToken).ConfigureAwait(false);
+
+ var exception = response.Exception ?? response.ResponseMessage?.MaybeException();
+
+ if (exception != null) {
+ return Options.ThrowOnAnyError ? throw exception : null;
+ }
+
+ if (response.ResponseMessage == null) return null;
+
+ if (request.ResponseWriter != null) {
+#if NETSTANDARD || NETFRAMEWORK
+ using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
+#else
+ await using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
+#endif
+ return request.ResponseWriter(stream!);
+ }
+
+ return await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
+ }
+
+ static RestResponse GetErrorResponse(RestRequest request, Exception exception, CancellationToken timeoutToken) {
+ var response = new RestResponse(request) {
+ ResponseStatus = exception is OperationCanceledException
+ ? TimedOut() ? ResponseStatus.TimedOut : ResponseStatus.Aborted
+ : ResponseStatus.Error,
+ ErrorMessage = exception.Message,
+ ErrorException = exception
+ };
+
+ return response;
+
+ bool TimedOut() => timeoutToken.IsCancellationRequested || exception.Message.Contains("HttpClient.Timeout");
+ }
+
+ async Task ExecuteRequestAsync(RestRequest request, CancellationToken cancellationToken) {
Ensure.NotNull(request, nameof(request));
// Make sure we are not disposed of when someone tries to call us!
@@ -56,7 +88,8 @@ async Task ExecuteInternal(RestRequest request, CancellationTo
using var requestContent = new RequestContent(this, request);
- if (Authenticator != null) await Authenticator.Authenticate(this, request).ConfigureAwait(false);
+ var authenticator = request.Authenticator ?? Options.Authenticator;
+ if (authenticator != null) await authenticator.Authenticate(this, request).ConfigureAwait(false);
var httpMethod = AsHttpMethod(request.Method);
var url = BuildUri(request);
@@ -72,6 +105,7 @@ async Task ExecuteInternal(RestRequest request, CancellationTo
try {
// Make sure we have a cookie container if not provided in the request
var cookieContainer = request.CookieContainer ??= new CookieContainer();
+
var headers = new RequestHeaders()
.AddHeaders(request.Parameters)
.AddHeaders(DefaultParameters)
@@ -84,67 +118,33 @@ async Task ExecuteInternal(RestRequest request, CancellationTo
var responseMessage = await HttpClient.SendAsync(message, request.CompletionOption, ct).ConfigureAwait(false);
// Parse all the cookies from the response and update the cookie jar with cookies
- if (responseMessage.Headers.TryGetValues("Set-Cookie", out var cookiesHeader)) {
+ if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) {
foreach (var header in cookiesHeader) {
- cookieContainer.SetCookies(url, header);
+ try {
+ cookieContainer.SetCookies(url, header);
+ }
+ catch (CookieException) {
+ // Do not fail request if we cannot parse a cookie
+ }
}
}
if (request.OnAfterRequest != null) await request.OnAfterRequest(responseMessage).ConfigureAwait(false);
- return new InternalResponse(responseMessage, url, null, timeoutCts.Token);
+ return new HttpResponse(responseMessage, url, cookieContainer, null, timeoutCts.Token);
}
catch (Exception ex) {
- return new InternalResponse(null, url, ex, timeoutCts.Token);
- }
- }
-
- record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception? Exception, CancellationToken TimeoutToken);
-
- ///
- /// A specialized method to download files as streams.
- ///
- /// Pre-configured request instance.
- ///
- /// The downloaded stream.
- [PublicAPI]
- public async Task DownloadStreamAsync(RestRequest request, CancellationToken cancellationToken = default) {
- // Make sure we only read the headers so we can stream the content body efficiently
- request.CompletionOption = HttpCompletionOption.ResponseHeadersRead;
- var response = await ExecuteInternal(request, cancellationToken).ConfigureAwait(false);
-
- var exception = response.Exception ?? response.ResponseMessage?.MaybeException();
-
- if (exception != null) {
- return Options.ThrowOnAnyError ? throw exception : null;
- }
-
- if (response.ResponseMessage == null) return null;
-
- if (request.ResponseWriter != null) {
-#if NETSTANDARD
- using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
-#else
- await using var stream = await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
-#endif
- return request.ResponseWriter(stream!);
+ return new HttpResponse(null, url, null, ex, timeoutCts.Token);
}
-
- return await response.ResponseMessage.ReadResponse(cancellationToken).ConfigureAwait(false);
}
- static RestResponse AddError(RestResponse response, Exception exception, CancellationToken timeoutToken) {
- response.ResponseStatus = exception is OperationCanceledException
- ? TimedOut() ? ResponseStatus.TimedOut : ResponseStatus.Aborted
- : ResponseStatus.Error;
-
- response.ErrorMessage = exception.Message;
- response.ErrorException = exception;
-
- return response;
-
- bool TimedOut() => timeoutToken.IsCancellationRequested || exception.Message.Contains("HttpClient.Timeout");
- }
+ record HttpResponse(
+ HttpResponseMessage? ResponseMessage,
+ Uri Url,
+ CookieContainer? CookieContainer,
+ Exception? Exception,
+ CancellationToken TimeoutToken
+ );
static HttpMethod AsHttpMethod(Method method)
=> method switch {
@@ -154,30 +154,14 @@ static HttpMethod AsHttpMethod(Method method)
Method.Delete => HttpMethod.Delete,
Method.Head => HttpMethod.Head,
Method.Options => HttpMethod.Options,
-#if NETSTANDARD
- Method.Patch => new HttpMethod("PATCH"),
-#else
+#if NET
Method.Patch => HttpMethod.Patch,
+#else
+ Method.Patch => new HttpMethod("PATCH"),
#endif
Method.Merge => new HttpMethod("MERGE"),
Method.Copy => new HttpMethod("COPY"),
Method.Search => new HttpMethod("SEARCH"),
- _ => throw new ArgumentOutOfRangeException()
+ _ => throw new ArgumentOutOfRangeException(nameof(method))
};
}
-
-public static class ResponseThrowExtension {
- public static RestResponse ThrowIfError(this RestResponse response) {
- var exception = response.GetException();
- if (exception != null) throw exception;
-
- return response;
- }
-
- public static RestResponse ThrowIfError(this RestResponse response) {
- var exception = response.GetException();
- if (exception != null) throw exception;
-
- return response;
- }
-}
diff --git a/src/RestSharp/RestClientExtensions.Config.cs b/src/RestSharp/RestClient.Extensions.Config.cs
similarity index 51%
rename from src/RestSharp/RestClientExtensions.Config.cs
rename to src/RestSharp/RestClient.Extensions.Config.cs
index 8868f7736..006bdc571 100644
--- a/src/RestSharp/RestClientExtensions.Config.cs
+++ b/src/RestSharp/RestClient.Extensions.Config.cs
@@ -21,27 +21,18 @@ namespace RestSharp;
public static partial class RestClientExtensions {
[PublicAPI]
- public static RestResponse Deserialize(this RestClient client, RestResponse response) => client.Deserialize(response.Request!, response);
+ public static RestResponse Deserialize(this IRestClient client, RestResponse response)
+ => client.Serializers.Deserialize(response.Request!, response, client.Options);
- ///
- /// Allows to use a custom way to encode URL parameters
- ///
- ///
- /// A delegate to encode URL parameters
- /// client.UseUrlEncoder(s => HttpUtility.UrlEncode(s));
- ///
- public static RestClient UseUrlEncoder(this RestClient client, Func encoder) => client.With(x => x.Encode = encoder);
+ [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");
- ///
- /// Allows to use a custom way to encode query parameters
- ///
- ///
- /// A delegate to encode query parameters
- /// client.UseUrlEncoder((s, encoding) => HttpUtility.UrlEncode(s, encoding));
- ///
+ [Obsolete("Set the RestClientOptions.EncodeQuery property instead")]
public static RestClient UseQueryEncoder(this RestClient client, Func queryEncoder)
- => client.With(x => x.EncodeQuery = 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)
- => client.With(x => x.Authenticator = authenticator);
-}
\ No newline at end of file
+ => throw new NotImplementedException("Set the RestClientOptions.Authenticator property instead");
+}
diff --git a/src/RestSharp/RestClientExtensions.Json.cs b/src/RestSharp/RestClient.Extensions.Json.cs
similarity index 100%
rename from src/RestSharp/RestClientExtensions.Json.cs
rename to src/RestSharp/RestClient.Extensions.Json.cs
diff --git a/src/RestSharp/RestClientExtensions.Params.cs b/src/RestSharp/RestClient.Extensions.Params.cs
similarity index 97%
rename from src/RestSharp/RestClientExtensions.Params.cs
rename to src/RestSharp/RestClient.Extensions.Params.cs
index 87f1b4e63..350c088c5 100644
--- a/src/RestSharp/RestClientExtensions.Params.cs
+++ b/src/RestSharp/RestClient.Extensions.Params.cs
@@ -42,6 +42,7 @@ public static RestClient AddDefaultParameter(this RestClient client, string name
/// This request
public static RestClient AddDefaultParameter(this RestClient client, string name, object value, ParameterType type) {
if (type == ParameterType.RequestBody) throw new ArgumentException("Default parameter cannot be Body", nameof(type));
+
return client.AddDefaultParameter(Parameter.CreateParameter(name, value, type));
}
@@ -62,8 +63,7 @@ public static RestClient AddDefaultHeader(this RestClient client, string name, s
/// Dictionary containing the Names and Values of the headers to add
///
public static RestClient AddDefaultHeaders(this RestClient client, Dictionary headers) {
- foreach (var header in headers)
- client.AddDefaultParameter(new HeaderParameter(header.Key, header.Value));
+ foreach (var header in headers) client.AddDefaultParameter(new HeaderParameter(header.Key, header.Value));
return client;
}
@@ -87,4 +87,4 @@ public static RestClient AddDefaultUrlSegment(this RestClient client, string nam
///
public static RestClient AddDefaultQueryParameter(this RestClient client, string name, string value)
=> client.AddDefaultParameter(new QueryParameter(name, value));
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/RestClientExtensions.cs b/src/RestSharp/RestClient.Extensions.cs
similarity index 79%
rename from src/RestSharp/RestClientExtensions.cs
rename to src/RestSharp/RestClient.Extensions.cs
index c91bf10b8..1ed035160 100644
--- a/src/RestSharp/RestClientExtensions.cs
+++ b/src/RestSharp/RestClient.Extensions.cs
@@ -29,7 +29,7 @@ public static partial class RestClientExtensions {
/// Request to be executed
/// Cancellation token
/// Deserialized response content
- public static Task> ExecuteGetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
+ public static Task> ExecuteGetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Get, cancellationToken);
///
@@ -38,7 +38,7 @@ public static Task> ExecuteGetAsync(this RestClient client, R
///
/// Request to be executed
/// Cancellation token
- public static Task ExecuteGetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
+ public static Task ExecuteGetAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Get, cancellationToken);
///
@@ -51,7 +51,7 @@ public static Task ExecuteGetAsync(this RestClient client, RestReq
/// The cancellation token
/// Deserialized response content
public static Task> ExecutePostAsync(
- this RestClient client,
+ this IRestClient client,
RestRequest request,
CancellationToken cancellationToken = default
)
@@ -63,7 +63,7 @@ public static Task> ExecutePostAsync(
///
/// Request to be executed
/// Cancellation token
- public static Task ExecutePostAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
+ public static Task ExecutePostAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Post, cancellationToken);
///
@@ -76,7 +76,7 @@ public static Task ExecutePostAsync(this RestClient client, RestRe
/// The cancellation token
/// Deserialized response content
public static Task> ExecutePutAsync(
- this RestClient client,
+ this IRestClient client,
RestRequest request,
CancellationToken cancellationToken = default
)
@@ -88,7 +88,7 @@ public static Task> ExecutePutAsync(
///
/// Request to be executed
/// Cancellation token
- public static Task ExecutePutAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default)
+ public static Task ExecutePutAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default)
=> client.ExecuteAsync(request, Method.Put, cancellationToken);
///
@@ -99,7 +99,7 @@ public static Task ExecutePutAsync(this RestClient client, RestReq
/// Request to be executed
/// Cancellation token
public static async Task> ExecuteAsync(
- this RestClient client,
+ this IRestClient client,
RestRequest request,
CancellationToken cancellationToken = default
) {
@@ -107,7 +107,7 @@ public static async Task> ExecuteAsync(
throw new ArgumentNullException(nameof(request));
var response = await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
- return client.Deserialize(request, response);
+ return client.Serializers.Deserialize(request, response, client.Options);
}
///
@@ -118,7 +118,7 @@ public static async Task> ExecuteAsync(
/// Override the request method
/// Cancellation token
public static Task ExecuteAsync(
- this RestClient client,
+ this IRestClient client,
RestRequest request,
Method httpMethod,
CancellationToken cancellationToken = default
@@ -138,7 +138,7 @@ public static Task ExecuteAsync(
/// Override the request method
/// Cancellation token
public static Task> ExecuteAsync(
- this RestClient client,
+ this IRestClient client,
RestRequest request,
Method httpMethod,
CancellationToken cancellationToken = default
@@ -158,12 +158,12 @@ public static Task> ExecuteAsync(
/// Cancellation token
/// Expected result type
///
- public static async Task GetAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -177,15 +177,15 @@ public static async Task GetAsync(this RestClient client, RestRequ
/// Cancellation token
/// Expected result type
///
- public static async Task PostAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request)
+ public static RestResponse Post(this IRestClient client, RestRequest request)
=> AsyncHelpers.RunSync(() => client.PostAsync(request));
- public static async Task PostAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -199,12 +199,12 @@ public static async Task PostAsync(this RestClient client, RestReq
/// Cancellation token
/// Expected result type
///
- public static async Task PutAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -218,12 +218,12 @@ public static async Task PutAsync(this RestClient client, RestRequ
/// Cancellation token
/// Expected result type
///
- public static async Task HeadAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -237,12 +237,12 @@ public static async Task HeadAsync(this RestClient client, RestReq
/// Cancellation token
/// Expected result type
///
- public static async Task OptionsAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -256,12 +256,12 @@ public static async Task OptionsAsync(this RestClient client, Rest
/// Cancellation token
/// Expected result type
///
- public static async Task PatchAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -275,12 +275,12 @@ public static async Task PatchAsync(this RestClient client, RestRe
/// Cancellation token
/// Expected result type
///
- public static async Task DeleteAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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 RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+ 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();
}
@@ -293,8 +293,8 @@ public static async Task DeleteAsync(this RestClient client, RestR
///
/// The downloaded file.
[PublicAPI]
- public static async Task DownloadDataAsync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) {
-#if NETSTANDARD
+ public static async Task DownloadDataAsync(this IRestClient client, RestRequest request, CancellationToken cancellationToken = default) {
+#if NETSTANDARD || NETFRAMEWORK
using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
#else
await using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
@@ -313,63 +313,34 @@ public static async Task DeleteAsync(this RestClient client, RestR
///
[PublicAPI]
public static async IAsyncEnumerable StreamJsonAsync(
- this RestClient client,
+ this IRestClient client,
string resource,
[EnumeratorCancellation] CancellationToken cancellationToken
) {
var request = new RestRequest(resource);
-#if NETSTANDARD
- using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
-#else
+#if NET
await using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
+#else
+ using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false);
#endif
if (stream == null) yield break;
- var serializer = client.Serializers[DataFormat.Json].GetSerializer();
+ var serializer = client.Serializers.GetSerializer(DataFormat.Json);
using var reader = new StreamReader(stream);
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested) {
+#if NET7_0
+ var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
+#else
var line = await reader.ReadLineAsync().ConfigureAwait(false);
+#endif
if (string.IsNullOrWhiteSpace(line)) continue;
- var response = new RestResponse { Content = line };
+ var response = new RestResponse(request) { Content = line };
yield return serializer.Deserializer.Deserialize(response)!;
}
}
- ///
- /// Sets the to only use JSON
- ///
- /// Client instance to work with
- /// Reference to the client instance
- public static RestClient UseJson(this RestClient client) {
- client.Serializers.Remove(DataFormat.Xml);
- client.AssignAcceptedContentTypes();
- return client;
- }
-
- ///
- /// Sets the to only use XML
- ///
- /// Client instance to work with
- /// Reference to the client instance
- public static RestClient UseXml(this RestClient client) {
- client.Serializers.Remove(DataFormat.Json);
- client.AssignAcceptedContentTypes();
- return client;
- }
-
- ///
- /// Sets the to only use the passed in custom serializer
- ///
- /// Client instance to work with
- /// Function that returns the serializer instance
- /// Reference to the client instance
- public static RestClient UseOnlySerializer(this RestClient client, Func serializerFactory) {
- client.Serializers.Clear();
- client.UseSerializer(serializerFactory);
- return client;
- }
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/RestClient.Serialization.cs b/src/RestSharp/RestClient.Serialization.cs
index c7e72aea0..d36954966 100644
--- a/src/RestSharp/RestClient.Serialization.cs
+++ b/src/RestSharp/RestClient.Serialization.cs
@@ -23,84 +23,4 @@
namespace RestSharp;
public partial class RestClient {
- internal Dictionary Serializers { get; } = new();
-
- ///
- /// Replace the default serializer with a custom one
- ///
- /// Function that returns the serializer instance
- public RestClient UseSerializer(Func serializerFactory) {
- var instance = serializerFactory();
-
- Serializers[instance.DataFormat] = new SerializerRecord(
- instance.DataFormat,
- instance.AcceptedContentTypes,
- instance.SupportsContentType,
- serializerFactory
- );
- AssignAcceptedContentTypes();
- return this;
- }
-
- public void UseDefaultSerializers() => UseSerializer().UseSerializer();
-
- ///
- /// Replace the default serializer with a custom one
- ///
- /// The type that implements
- ///
- public RestClient UseSerializer() where T : class, IRestSerializer, new() => UseSerializer(() => new T());
-
- internal RestResponse Deserialize(RestRequest request, RestResponse raw) {
- var response = RestResponse.FromResponse(raw);
-
- try {
- request.OnBeforeDeserialization?.Invoke(raw);
-
- // Only attempt to deserialize if the request has not errored due
- // to a transport or framework exception. HTTP errors should attempt to
- // be deserialized
- if (response.Content != null) {
- // Only continue if there is a handler defined else there is no way to deserialize the data.
- // This can happen when a request returns for example a 404 page instead of the requested JSON/XML resource
- var handler = GetContentDeserializer(raw, request.RequestFormat);
-
- if (handler is IXmlDeserializer xml && request is RestXmlRequest xmlRequest) {
- if (xmlRequest.XmlNamespace.IsNotEmpty()) xml.Namespace = xmlRequest.XmlNamespace!;
-
- if (xml is IWithDateFormat withDateFormat && xmlRequest.DateFormat.IsNotEmpty())
- withDateFormat.DateFormat = xmlRequest.DateFormat!;
- }
-
- if (handler != null) response.Data = handler.Deserialize(raw);
- }
- }
- catch (Exception ex) {
- if (Options.ThrowOnAnyError) throw;
-
- if (Options.FailOnDeserializationError || Options.ThrowOnDeserializationError) response.ResponseStatus = ResponseStatus.Error;
-
- response.ErrorMessage = ex.Message;
- response.ErrorException = ex;
-
- if (Options.ThrowOnDeserializationError) throw new DeserializationException(response, ex);
- }
-
- response.Request = request;
-
- return response;
- }
-
- IDeserializer? GetContentDeserializer(RestResponseBase response, DataFormat requestFormat) {
- var contentType = response.ContentType ?? DetectContentType();
- if (contentType == null) return null;
-
- var serializer = Serializers.Values.FirstOrDefault(x => x.SupportsContentType(contentType));
- var factory = serializer ?? (Serializers.ContainsKey(requestFormat) ? Serializers[requestFormat] : null);
- return factory?.GetSerializer().Deserializer;
-
- string? DetectContentType()
- => response.Content!.StartsWith("<") ? ContentType.Xml
- : response.Content.StartsWith("{") || response.Content.StartsWith("[") ? ContentType.Json : null;
- }
}
\ No newline at end of file
diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs
index 7202c4fa0..f4fe00932 100644
--- a/src/RestSharp/RestClient.cs
+++ b/src/RestSharp/RestClient.cs
@@ -12,110 +12,184 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using System.Net;
using System.Net.Http.Headers;
-using System.Text;
using RestSharp.Authenticators;
-using RestSharp.Extensions;
+using RestSharp.Serializers;
// ReSharper disable VirtualMemberCallInConstructor
#pragma warning disable 618
namespace RestSharp;
+public delegate void ConfigureHeaders(HttpRequestHeaders headers);
+
+public delegate void ConfigureSerialization(SerializerConfig config);
+
+public delegate void ConfigureRestClient(RestClientOptions options);
+
///
/// Client to translate RestRequests into Http requests and process response result
///
-public partial class RestClient : IDisposable {
+public partial class RestClient : IRestClient {
///
/// Content types that will be sent in the Accept header. The list is populated from the known serializers.
/// If you need to send something else by default, set this property to a different value.
///
public string[] AcceptedContentTypes { get; set; } = null!;
- ///
- /// Function to calculate the response status. By default, the status will be Completed if it was successful, or NotFound.
- ///
- public CalculateResponseStatus CalculateResponseStatus { get; set; } = httpResponse
- => httpResponse.IsSuccessStatusCode || httpResponse.StatusCode == HttpStatusCode.NotFound
- ? ResponseStatus.Completed
- : ResponseStatus.Error;
+ internal HttpClient HttpClient { get; }
- HttpClient HttpClient { get; }
+ /// >
+ public ReadOnlyRestClientOptions Options { get; }
- public RestClientOptions Options { get; }
+ /// >
+ public RestSerializers Serializers { get; }
- public RestClient(RestClientOptions options, Action? configureDefaultHeaders = null) {
- UseDefaultSerializers();
+ [Obsolete("Use RestClientOptions.Authenticator instead")]
+ public IAuthenticator? Authenticator => Options.Authenticator;
- Options = options;
- _disposeHttpClient = true;
+ // set => Options.Authenticator = value;
+ ///
+ /// Creates an instance of RestClient using the provided
+ ///
+ /// Client options
+ /// Delegate to add default headers to the wrapped HttpClient instance
+ /// Delegate to configure serialization
+ /// Set to true if you wish to reuse the instance
+ public RestClient(
+ RestClientOptions options,
+ ConfigureHeaders? configureDefaultHeaders = null,
+ ConfigureSerialization? configureSerialization = null,
+ bool useClientFactory = false
+ ) {
+ if (useClientFactory && options.BaseUrl == null) {
+ throw new ArgumentException("BaseUrl must be set when using a client factory");
+ }
+
+ Serializers = new RestSerializers(ConfigureSerializers(configureSerialization));
+ Options = new ReadOnlyRestClientOptions(options);
+
+ if (useClientFactory) {
+ _disposeHttpClient = false;
+ HttpClient = SimpleClientFactory.GetClient(options.BaseUrl!, GetClient);
+ }
+ else {
+ _disposeHttpClient = true;
+ HttpClient = GetClient();
+ }
- var handler = new HttpClientHandler();
- ConfigureHttpMessageHandler(handler);
+ HttpClient GetClient() {
+ var handler = new HttpClientHandler();
+ ConfigureHttpMessageHandler(handler, Options);
+ var finalHandler = options.ConfigureMessageHandler?.Invoke(handler) ?? handler;
- var finalHandler = Options.ConfigureMessageHandler?.Invoke(handler) ?? handler;
+ var httpClient = new HttpClient(finalHandler);
+ ConfigureHttpClient(httpClient, options);
+ configureDefaultHeaders?.Invoke(httpClient.DefaultRequestHeaders);
+ return httpClient;
+ }
+ }
- HttpClient = new HttpClient(finalHandler);
- ConfigureHttpClient(HttpClient);
- configureDefaultHeaders?.Invoke(HttpClient.DefaultRequestHeaders);
+ static RestClientOptions ConfigureOptions(RestClientOptions options, ConfigureRestClient? configureRestClient) {
+ configureRestClient?.Invoke(options);
+ return options;
}
///
/// Creates an instance of RestClient using the default
///
- public RestClient() : this(new RestClientOptions()) { }
+ /// Delegate to configure the client options
+ /// Delegate to add default headers to the wrapped HttpClient instance
+ /// Delegate to configure serialization
+ /// Set to true if you wish to reuse the instance
+ public RestClient(
+ ConfigureRestClient? configureRestClient = null,
+ ConfigureHeaders? configureDefaultHeaders = null,
+ ConfigureSerialization? configureSerialization = null,
+ bool useClientFactory = false
+ )
+ : this(ConfigureOptions(new RestClientOptions(), configureRestClient), configureDefaultHeaders, configureSerialization, useClientFactory) { }
///
///
/// Creates an instance of RestClient using a specific BaseUrl for requests made by this client instance
///
/// Base URI for the new client
- public RestClient(Uri baseUrl) : this(new RestClientOptions { BaseUrl = baseUrl }) { }
+ /// Delegate to configure the client options
+ /// Delegate to add default headers to the wrapped HttpClient instance
+ /// Delegate to configure serialization
+ /// Set to true if you wish to reuse the instance
+ public RestClient(
+ Uri baseUrl,
+ ConfigureRestClient? configureRestClient = null,
+ ConfigureHeaders? configureDefaultHeaders = null,
+ ConfigureSerialization? configureSerialization = null,
+ bool useClientFactory = false
+ )
+ : this(
+ ConfigureOptions(new RestClientOptions { BaseUrl = baseUrl }, configureRestClient),
+ configureDefaultHeaders,
+ configureSerialization,
+ useClientFactory
+ ) { }
- ///
///
/// Creates an instance of RestClient using a specific BaseUrl for requests made by this client instance
///
/// Base URI for this new client as a string
- public RestClient(string baseUrl) : this(new Uri(Ensure.NotEmptyString(baseUrl, nameof(baseUrl)))) { }
+ /// Delegate to configure the client options
+ /// Delegate to add default headers to the wrapped HttpClient instance
+ /// Delegate to configure serialization
+ public RestClient(
+ string baseUrl,
+ ConfigureRestClient? configureRestClient = null,
+ ConfigureHeaders? configureDefaultHeaders = null,
+ ConfigureSerialization? configureSerialization = null
+ )
+ : this(new Uri(Ensure.NotEmptyString(baseUrl, nameof(baseUrl))), configureRestClient, configureDefaultHeaders, configureSerialization) { }
///
- /// Creates an instance of RestClient using a shared HttpClient and does not allocate one internally.
+ /// Creates an instance of RestClient using a shared HttpClient and specific RestClientOptions and does not allocate one internally.
///
/// HttpClient to use
+ /// RestClient options to use
/// True to dispose of the client, false to assume the caller does (defaults to false)
- public RestClient(HttpClient httpClient, bool disposeHttpClient = false) {
- UseDefaultSerializers();
+ /// Delegate to configure serialization
+ public RestClient(
+ HttpClient httpClient,
+ RestClientOptions? options,
+ bool disposeHttpClient = false,
+ ConfigureSerialization? configureSerialization = null
+ ) {
+ Serializers = new RestSerializers(ConfigureSerializers(configureSerialization));
HttpClient = httpClient;
- Options = new RestClientOptions();
_disposeHttpClient = disposeHttpClient;
- if (httpClient.BaseAddress != null) {
- Options.BaseUrl = httpClient.BaseAddress;
+ if (httpClient.BaseAddress != null && options != null && options.BaseUrl == null) {
+ options.BaseUrl = httpClient.BaseAddress;
}
+
+ var opt = options ?? new RestClientOptions();
+ Options = new ReadOnlyRestClientOptions(opt);
+
+ if (options != null) ConfigureHttpClient(httpClient, options);
}
///
- /// Creates an instance of RestClient using a shared HttpClient and specific RestClientOptions and does not allocate one internally.
+ /// Creates an instance of RestClient using a shared HttpClient and does not allocate one internally.
///
/// HttpClient to use
- /// RestClient options to use
/// True to dispose of the client, false to assume the caller does (defaults to false)
- public RestClient(HttpClient httpClient, RestClientOptions options, bool disposeHttpClient = false) {
- UseDefaultSerializers();
-
- HttpClient = httpClient;
- Options = options;
- _disposeHttpClient = disposeHttpClient;
-
- if (httpClient.BaseAddress != null && options.BaseUrl == null) {
- Options.BaseUrl = httpClient.BaseAddress;
- }
-
- ConfigureHttpClient(HttpClient);
- }
+ /// Delegate to configure the client options
+ /// Delegate to configure serialization
+ public RestClient(
+ HttpClient httpClient,
+ bool disposeHttpClient = false,
+ ConfigureRestClient? configureRestClient = null,
+ ConfigureSerialization? configureSerialization = null
+ )
+ : this(httpClient, ConfigureOptions(new RestClientOptions(), configureRestClient), disposeHttpClient, configureSerialization) { }
///
/// Creates a new instance of RestSharp using the message handler provided. By default, HttpClient disposes the provided handler
@@ -123,85 +197,88 @@ public RestClient(HttpClient httpClient, RestClientOptions options, bool dispose
///
/// Message handler instance to use for HttpClient
/// Dispose the handler when disposing RestClient, true by default
- public RestClient(HttpMessageHandler handler, bool disposeHandler = true) : this(new HttpClient(handler, disposeHandler), true) { }
-
- void ConfigureHttpClient(HttpClient httpClient) {
- if (Options.MaxTimeout > 0) httpClient.Timeout = TimeSpan.FromMilliseconds(Options.MaxTimeout);
-
- if (Options.UserAgent != null && httpClient.DefaultRequestHeaders.UserAgent.All(x => x.Product?.Name != Options.UserAgent)) {
- httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Options.UserAgent);
+ /// Delegate to configure the client options
+ /// Delegate to configure serialization
+ public RestClient(
+ HttpMessageHandler handler,
+ bool disposeHandler = true,
+ ConfigureRestClient? configureRestClient = null,
+ ConfigureSerialization? configureSerialization = null
+ )
+ : this(new HttpClient(handler, disposeHandler), true, configureRestClient, configureSerialization) { }
+
+ static void ConfigureHttpClient(HttpClient httpClient, RestClientOptions options) {
+ if (options.MaxTimeout > 0) httpClient.Timeout = TimeSpan.FromMilliseconds(options.MaxTimeout);
+
+ if (options.UserAgent != null && httpClient.DefaultRequestHeaders.UserAgent.All(x => x.Product?.Name != options.UserAgent)) {
+ httpClient.DefaultRequestHeaders.TryAddWithoutValidation(KnownHeaders.UserAgent, options.UserAgent);
}
- if (Options.Expect100Continue != null) httpClient.DefaultRequestHeaders.ExpectContinue = Options.Expect100Continue;
+ if (options.Expect100Continue != null) httpClient.DefaultRequestHeaders.ExpectContinue = options.Expect100Continue;
}
- void ConfigureHttpMessageHandler(HttpClientHandler handler) {
+ static void ConfigureHttpMessageHandler(HttpClientHandler handler, ReadOnlyRestClientOptions options) {
handler.UseCookies = false;
- handler.Credentials = Options.Credentials;
- handler.UseDefaultCredentials = Options.UseDefaultCredentials;
- handler.AutomaticDecompression = Options.AutomaticDecompression;
- handler.PreAuthenticate = Options.PreAuthenticate;
- handler.AllowAutoRedirect = Options.FollowRedirects;
+ handler.Credentials = options.Credentials;
+ handler.UseDefaultCredentials = options.UseDefaultCredentials;
+ handler.AutomaticDecompression = options.AutomaticDecompression;
+ handler.PreAuthenticate = options.PreAuthenticate;
+ handler.AllowAutoRedirect = options.FollowRedirects;
- if (handler.SupportsProxy) handler.Proxy = Options.Proxy;
+ if (handler.SupportsProxy) handler.Proxy = options.Proxy;
- if (Options.RemoteCertificateValidationCallback != null)
+ if (options.RemoteCertificateValidationCallback != null)
handler.ServerCertificateCustomValidationCallback =
- (request, cert, chain, errors) => Options.RemoteCertificateValidationCallback(request, cert, chain, errors);
+ (request, cert, chain, errors) => options.RemoteCertificateValidationCallback(request, cert, chain, errors);
- if (Options.ClientCertificates != null) {
- handler.ClientCertificates.AddRange(Options.ClientCertificates);
+ if (options.ClientCertificates != null) {
+ handler.ClientCertificates.AddRange(options.ClientCertificates);
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}
- if (Options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = Options.MaxRedirects.Value;
+ if (options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = options.MaxRedirects.Value;
}
- internal Func Encode { get; set; } = s => s.UrlEncode();
-
- internal Func EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding)!;
-
- ///
- /// Authenticator that will be used to populate request with necessary authentication data
- ///
- public IAuthenticator? Authenticator { get; set; }
-
public ParametersCollection DefaultParameters { get; } = new();
+ readonly object _lock = new();
+
///
/// Add a parameter to use on every request made with this client instance
///
/// Parameter to add
///
public RestClient AddDefaultParameter(Parameter parameter) {
- if (parameter.Type == ParameterType.RequestBody)
- throw new NotSupportedException(
- "Cannot set request body using default parameters. Use Request.AddBody() instead."
- );
-
- if (!Options.AllowMultipleDefaultParametersWithSameName &&
- !MultiParameterTypes.Contains(parameter.Type) &&
- DefaultParameters.Any(x => x.Name == parameter.Name)) {
- throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter));
+ lock (_lock) {
+ if (parameter.Type == ParameterType.RequestBody)
+ throw new NotSupportedException(
+ "Cannot set request body using default parameters. Use Request.AddBody() instead."
+ );
+
+ if (!Options.AllowMultipleDefaultParametersWithSameName &&
+ !MultiParameterTypes.Contains(parameter.Type) &&
+ DefaultParameters.Any(x => x.Name == parameter.Name)) {
+ throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter));
+ }
+
+ DefaultParameters.AddParameter(parameter);
}
- DefaultParameters.AddParameter(parameter);
-
return this;
}
static readonly ParameterType[] MultiParameterTypes = { ParameterType.QueryString, ParameterType.GetOrPost };
- public Uri BuildUri(RestRequest request) {
+ internal Uri BuildUri(RestRequest request) {
DoBuildUriValidations(request);
- var (uri, resource) = Options.BaseUrl.GetUrlSegmentParamsValues(request.Resource, Encode, request.Parameters, DefaultParameters);
+ var (uri, resource) = Options.BaseUrl.GetUrlSegmentParamsValues(request.Resource, Options.Encode, request.Parameters, DefaultParameters);
var mergedUri = uri.MergeBaseUrlAndResource(resource);
var finalUri = mergedUri.ApplyQueryStringParamsValuesToUri(
request.Method,
Options.Encoding,
- EncodeQuery,
+ Options.EncodeQuery,
request.Parameters,
DefaultParameters
);
@@ -210,12 +287,11 @@ public Uri BuildUri(RestRequest request) {
internal Uri BuildUriWithoutQueryParameters(RestRequest request) {
DoBuildUriValidations(request);
- var (uri, resource) = Options.BaseUrl.GetUrlSegmentParamsValues(request.Resource, Encode, request.Parameters, DefaultParameters);
+ var (uri, resource) = Options.BaseUrl.GetUrlSegmentParamsValues(request.Resource, Options.Encode, request.Parameters, DefaultParameters);
return uri.MergeBaseUrlAndResource(resource);
}
- internal void AssignAcceptedContentTypes()
- => AcceptedContentTypes = Serializers.SelectMany(x => x.Value.AcceptedContentTypes).Distinct().ToArray();
+ internal void AssignAcceptedContentTypes(SerializerConfig serializerConfig) => AcceptedContentTypes = serializerConfig.GetAcceptedContentTypes();
void DoBuildUriValidations(RestRequest request) {
if (Options.BaseUrl == null && !request.Resource.ToLowerInvariant().StartsWith("http"))
@@ -225,6 +301,13 @@ void DoBuildUriValidations(RestRequest request) {
);
}
+ SerializerConfig ConfigureSerializers(ConfigureSerialization? configureSerialization) {
+ var serializerConfig = new SerializerConfig(this);
+ serializerConfig.UseDefaultSerializers();
+ configureSerialization?.Invoke(serializerConfig);
+ return serializerConfig;
+ }
+
readonly bool _disposeHttpClient;
bool _disposed;
diff --git a/src/RestSharp/RestSharp.csproj b/src/RestSharp/RestSharp.csproj
index 55f34844c..00af3c686 100644
--- a/src/RestSharp/RestSharp.csproj
+++ b/src/RestSharp/RestSharp.csproj
@@ -1,8 +1,36 @@
-
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ RestClient.Extensions.cs
+
+
+ RestClient.Extensions.cs
+
+
+ RestClient.Extensions.cs
+
+
+ RestClient.cs
+
+
+
+
+
diff --git a/src/RestSharp/RestSharp.csproj.DotSettings b/src/RestSharp/RestSharp.csproj.DotSettings
index 1f7de081c..40f3c00e7 100644
--- a/src/RestSharp/RestSharp.csproj.DotSettings
+++ b/src/RestSharp/RestSharp.csproj.DotSettings
@@ -1,5 +1,6 @@
True
+ True
True
True
True
diff --git a/src/RestSharp/Serializers/ContentType.cs b/src/RestSharp/Serializers/ContentType.cs
deleted file mode 100644
index 0306719da..000000000
--- a/src/RestSharp/Serializers/ContentType.cs
+++ /dev/null
@@ -1,41 +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.Serializers;
-
-public delegate bool SupportsContentType(string contentType);
-
-public static class ContentType {
- public const string Json = "application/json";
- public const string Xml = "application/xml";
- public const string Plain = "text/plain";
- public const string Binary = "application/octet-stream";
- public const string GZip = "application/x-gzip";
-
- public static readonly Dictionary FromDataFormat =
- new() {
- { DataFormat.Xml, Xml },
- { DataFormat.Json, Json },
- { DataFormat.None, Plain },
- { DataFormat.Binary, Binary }
- };
-
- public static readonly string[] JsonAccept = {
- "application/json", "text/json", "text/x-json", "text/javascript"
- };
-
- public static readonly string[] XmlAccept = {
- "application/xml", "text/xml"
- };
-}
\ No newline at end of file
diff --git a/src/RestSharp/Serializers/ISerializer.cs b/src/RestSharp/Serializers/ISerializer.cs
index febd52dcf..6085f0380 100644
--- a/src/RestSharp/Serializers/ISerializer.cs
+++ b/src/RestSharp/Serializers/ISerializer.cs
@@ -15,7 +15,7 @@
namespace RestSharp.Serializers;
public interface ISerializer {
- string ContentType { get; set; }
+ ContentType ContentType { get; set; }
string? Serialize(object obj);
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/Serializers/Json/RestClientExtensions.cs b/src/RestSharp/Serializers/Json/RestClientExtensions.cs
index 5823ebf61..ec0fa24b9 100644
--- a/src/RestSharp/Serializers/Json/RestClientExtensions.cs
+++ b/src/RestSharp/Serializers/Json/RestClientExtensions.cs
@@ -14,23 +14,24 @@
using System.Text.Json;
-namespace RestSharp.Serializers.Json;
+namespace RestSharp.Serializers.Json;
[PublicAPI]
public static class RestClientExtensions {
///
/// Use System.Text.Json serializer with default settings
///
- ///
+ ///
///
- public static RestClient UseSystemTextJson(this RestClient client) => client.UseSerializer(() => new SystemTextJsonSerializer());
+ public static SerializerConfig UseSystemTextJson(this SerializerConfig serializerConfig)
+ => serializerConfig.UseSerializer(() => new SystemTextJsonSerializer());
///
/// Use System.Text.Json serializer with custom settings
///
- ///
+ ///
/// System.Text.Json serializer options
///
- public static RestClient UseSystemTextJson(this RestClient client, JsonSerializerOptions options)
- => client.UseSerializer(() => new SystemTextJsonSerializer(options));
-}
\ No newline at end of file
+ public static SerializerConfig UseSystemTextJson(this SerializerConfig serializerConfig, JsonSerializerOptions options)
+ => serializerConfig.UseSerializer(() => new SystemTextJsonSerializer(options));
+}
diff --git a/src/RestSharp/Serializers/Json/SystemTextJsonSerializer.cs b/src/RestSharp/Serializers/Json/SystemTextJsonSerializer.cs
index 09e05dc03..869167163 100644
--- a/src/RestSharp/Serializers/Json/SystemTextJsonSerializer.cs
+++ b/src/RestSharp/Serializers/Json/SystemTextJsonSerializer.cs
@@ -36,11 +36,11 @@ public class SystemTextJsonSerializer : IRestSerializer, ISerializer, IDeseriali
public T? Deserialize(RestResponse response) => JsonSerializer.Deserialize(response.Content!, _options);
- public string ContentType { get; set; } = "application/json";
+ public ContentType ContentType { get; set; } = ContentType.Json;
- public ISerializer Serializer => this;
- public IDeserializer Deserializer => this;
- public DataFormat DataFormat => DataFormat.Json;
- public string[] AcceptedContentTypes => Serializers.ContentType.JsonAccept;
- public SupportsContentType SupportsContentType => contentType => contentType.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
+ public ISerializer Serializer => this;
+ public IDeserializer Deserializer => this;
+ public DataFormat DataFormat => DataFormat.Json;
+ public string[] AcceptedContentTypes => ContentType.JsonAccept;
+ public SupportsContentType SupportsContentType => contentType => contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
}
\ No newline at end of file
diff --git a/src/RestSharp/Serializers/RestSerializers.cs b/src/RestSharp/Serializers/RestSerializers.cs
new file mode 100644
index 000000000..c946aaa4e
--- /dev/null
+++ b/src/RestSharp/Serializers/RestSerializers.cs
@@ -0,0 +1,102 @@
+// 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.Collections.ObjectModel;
+using RestSharp.Extensions;
+using RestSharp.Serializers.Xml;
+
+namespace RestSharp.Serializers;
+
+public class RestSerializers {
+ public IReadOnlyDictionary Serializers { get; }
+
+ public RestSerializers(Dictionary records)
+ => Serializers = new ReadOnlyDictionary(records);
+
+ public RestSerializers(SerializerConfig config) : this(config.Serializers) { }
+
+ public IRestSerializer GetSerializer(DataFormat dataFormat)
+ => Serializers.TryGetValue(dataFormat, out var value)
+ ? value.GetSerializer()
+ : throw new InvalidOperationException($"Unable to find a serializer for {dataFormat}");
+
+ internal RestResponse Deserialize(RestRequest request, RestResponse raw, ReadOnlyRestClientOptions options) {
+ var response = RestResponse.FromResponse(raw);
+
+ try {
+ request.OnBeforeDeserialization?.Invoke(raw);
+ response.Data = DeserializeContent(raw);
+ }
+ catch (Exception ex) {
+ if (options.ThrowOnAnyError) throw;
+
+ if (options.FailOnDeserializationError || options.ThrowOnDeserializationError) response.ResponseStatus = ResponseStatus.Error;
+
+ response.AddException(ex);
+
+ if (options.ThrowOnDeserializationError) throw new DeserializationException(response, ex);
+ }
+
+ return response;
+ }
+
+ ///
+ /// Deserialize the response content into the specified type
+ ///
+ /// Response instance
+ /// Deserialized model type
+ ///
+ [PublicAPI]
+ public T? DeserializeContent(RestResponse response) {
+ // Only attempt to deserialize if the request has not errored due
+ // to a transport or framework exception. HTTP errors should attempt to
+ // be deserialized
+ if (response.Content == null) {
+ return default;
+ }
+
+ // Only continue if there is a handler defined else there is no way to deserialize the data.
+ // This can happen when a request returns for example a 404 page instead of the requested JSON/XML resource
+ var deserializer = GetContentDeserializer(response);
+
+ if (deserializer is IXmlDeserializer xml && response.Request is RestXmlRequest xmlRequest) {
+ if (xmlRequest.XmlNamespace.IsNotEmpty()) xml.Namespace = xmlRequest.XmlNamespace!;
+
+ if (xml is IWithDateFormat withDateFormat && xmlRequest.DateFormat.IsNotEmpty()) withDateFormat.DateFormat = xmlRequest.DateFormat!;
+ }
+
+ return deserializer != null ? deserializer.Deserialize(response) : default;
+ }
+
+ IDeserializer? GetContentDeserializer(RestResponseBase response) {
+ if (string.IsNullOrWhiteSpace(response.Content)) return null;
+
+ var contentType = response.ContentType ?? DetectContentType()?.Value;
+ if (contentType == null) return null;
+
+ var serializer = Serializers.Values.FirstOrDefault(x => x.SupportsContentType(contentType));
+
+ var factory = serializer ??
+ (Serializers.ContainsKey(response.Request.RequestFormat) ? Serializers[response.Request.RequestFormat] : null);
+ return factory?.GetSerializer().Deserializer;
+
+ ContentType? DetectContentType()
+ => response.Content![0] switch {
+ '<' => ContentType.Xml,
+ '{' or '[' => ContentType.Json,
+ _ => null
+ };
+ }
+}
diff --git a/src/RestSharp/Serializers/SerializerConfig.cs b/src/RestSharp/Serializers/SerializerConfig.cs
new file mode 100644
index 000000000..986875208
--- /dev/null
+++ b/src/RestSharp/Serializers/SerializerConfig.cs
@@ -0,0 +1,91 @@
+// 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.Serializers.Json;
+using RestSharp.Serializers.Xml;
+
+namespace RestSharp.Serializers;
+
+public class SerializerConfig {
+ internal RestClient Client { get; }
+
+ internal Dictionary Serializers { get; } = new();
+
+ internal SerializerConfig(RestClient client) => Client = client;
+
+ ///
+ /// Replace the default serializer with a custom one
+ ///
+ /// Function that returns the serializer instance
+ public SerializerConfig UseSerializer(Func serializerFactory) {
+ var instance = serializerFactory();
+
+ Serializers[instance.DataFormat] = new SerializerRecord(
+ instance.DataFormat,
+ instance.AcceptedContentTypes,
+ instance.SupportsContentType,
+ serializerFactory
+ );
+ Client.AssignAcceptedContentTypes(this);
+ return this;
+ }
+
+ public void UseDefaultSerializers() => UseSerializer().UseSerializer();
+
+ ///
+ /// Replace the default serializer with a custom one
+ ///
+ /// The type that implements
+ ///
+ public SerializerConfig UseSerializer() where T : class, IRestSerializer, new() => UseSerializer(() => new T());
+
+ internal string[] GetAcceptedContentTypes() => Serializers.SelectMany(x => x.Value.AcceptedContentTypes).Distinct().ToArray();
+}
+
+public static class SerializerConfigExtensions {
+ ///
+ /// Sets the to only use JSON
+ ///
+ /// Configuration instance to work with
+ /// Reference to the client instance
+ public static SerializerConfig UseJson(this SerializerConfig config) {
+ config.Serializers.Remove(DataFormat.Xml);
+ config.Client.AssignAcceptedContentTypes(config);
+ return config;
+ }
+
+ ///
+ /// Sets the to only use XML
+ ///
+ /// Configuration instance to work with
+ /// Reference to the client instance
+ public static SerializerConfig UseXml(this SerializerConfig config) {
+ config.Serializers.Remove(DataFormat.Json);
+ config.Client.AssignAcceptedContentTypes(config);
+ return config;
+ }
+
+ ///
+ /// Sets the to only use the passed in custom serializer
+ ///
+ /// Configuration instance to work with
+ /// Function that returns the serializer instance
+ /// Reference to the client instance
+ public static SerializerConfig UseOnlySerializer(this SerializerConfig config, Func serializerFactory) {
+ config.Serializers.Clear();
+ config.UseSerializer(serializerFactory);
+ return config;
+ }
+}
diff --git a/src/RestSharp/Serializers/Xml/DotNetXmlSerializer.cs b/src/RestSharp/Serializers/Xml/DotNetXmlSerializer.cs
index d684cc9af..dd9c8407a 100644
--- a/src/RestSharp/Serializers/Xml/DotNetXmlSerializer.cs
+++ b/src/RestSharp/Serializers/Xml/DotNetXmlSerializer.cs
@@ -25,7 +25,7 @@ public class DotNetXmlSerializer : IXmlSerializer {
/// Default constructor, does not specify namespace
///
public DotNetXmlSerializer() {
- ContentType = Serializers.ContentType.Xml;
+ ContentType = RestSharp.ContentType.Xml;
Encoding = Encoding.UTF8;
}
@@ -79,7 +79,7 @@ public string Serialize(object obj) {
///
/// Content type for serialized content
///
- public string ContentType { get; set; }
+ public ContentType ContentType { get; set; }
class EncodingStringWriter : StringWriter {
// Need to subclass StringWriter in order to override Encoding
diff --git a/src/RestSharp/Serializers/Xml/DotNetXmlSerializerClientExtensions.cs b/src/RestSharp/Serializers/Xml/DotNetXmlSerializerClientExtensions.cs
index 87ce5a36e..2a6d20d64 100644
--- a/src/RestSharp/Serializers/Xml/DotNetXmlSerializerClientExtensions.cs
+++ b/src/RestSharp/Serializers/Xml/DotNetXmlSerializerClientExtensions.cs
@@ -14,18 +14,18 @@
using System.Text;
-namespace RestSharp.Serializers.Xml;
+namespace RestSharp.Serializers.Xml;
[PublicAPI]
public static class DotNetXmlSerializerClientExtensions {
- public static RestClient UseDotNetXmlSerializer(
- this RestClient restClient,
- string? xmlNamespace = null,
- Encoding? encoding = null
+ public static SerializerConfig UseDotNetXmlSerializer(
+ this SerializerConfig serializerConfig,
+ string? xmlNamespace = null,
+ Encoding? encoding = null
) {
var xmlSerializer = new DotNetXmlSerializer();
if (xmlNamespace != null) xmlSerializer.Namespace = xmlNamespace;
- if (encoding != null) xmlSerializer.Encoding = encoding;
+ if (encoding != null) xmlSerializer.Encoding = encoding;
var xmlDeserializer = new DotNetXmlDeserializer();
if (encoding != null) xmlDeserializer.Encoding = encoding;
@@ -34,6 +34,6 @@ public static RestClient UseDotNetXmlSerializer(
.WithXmlSerializer(xmlSerializer)
.WithXmlDeserializer(xmlDeserializer);
- return restClient.UseSerializer(() => serializer);
+ return serializerConfig.UseSerializer(() => serializer);
}
-}
\ No newline at end of file
+}
diff --git a/src/RestSharp/Serializers/Xml/XmlRestSerializer.cs b/src/RestSharp/Serializers/Xml/XmlRestSerializer.cs
index 37dcb94c1..85b2688cf 100644
--- a/src/RestSharp/Serializers/Xml/XmlRestSerializer.cs
+++ b/src/RestSharp/Serializers/Xml/XmlRestSerializer.cs
@@ -28,7 +28,7 @@ public XmlRestSerializer(IXmlSerializer xmlSerializer, IXmlDeserializer xmlDeser
public ISerializer Serializer => _xmlSerializer;
public IDeserializer Deserializer => _xmlDeserializer;
public string[] AcceptedContentTypes => ContentType.XmlAccept;
- public SupportsContentType SupportsContentType => contentType => contentType.EndsWith("xml", StringComparison.InvariantCultureIgnoreCase);
+ public SupportsContentType SupportsContentType => contentType => contentType.Value.EndsWith("xml", StringComparison.InvariantCultureIgnoreCase);
public DataFormat DataFormat => DataFormat.Xml;
diff --git a/src/RestSharp/SimpleClientFactory.cs b/src/RestSharp/SimpleClientFactory.cs
new file mode 100644
index 000000000..46f5d6273
--- /dev/null
+++ b/src/RestSharp/SimpleClientFactory.cs
@@ -0,0 +1,30 @@
+// 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.Collections.Concurrent;
+
+namespace RestSharp;
+
+static class SimpleClientFactory {
+ static readonly ConcurrentDictionary CachedClients = new();
+
+ public static HttpClient GetClient(Uri baseUrl, Func getClient) {
+ var key = baseUrl.ToString();
+ if (CachedClients.TryGetValue(key, out var client)) return client;
+ client = getClient();
+ CachedClients.TryAdd(key, client);
+ return client;
+ }
+}
diff --git a/src/RestSharp/Sync/RestClient.Sync.cs b/src/RestSharp/Sync/RestClient.Sync.cs
index 0c982ddfb..50b4e6557 100644
--- a/src/RestSharp/Sync/RestClient.Sync.cs
+++ b/src/RestSharp/Sync/RestClient.Sync.cs
@@ -15,22 +15,24 @@
namespace RestSharp;
-public partial class RestClient {
+public static partial class RestClientExtensions {
///
/// Executes the request synchronously, authenticating if needed
///
+ ///
/// Request to be executed
/// The cancellation token
- public RestResponse Execute(RestRequest request, CancellationToken cancellationToken = default)
- => AsyncHelpers.RunSync(() => ExecuteAsync(request, cancellationToken));
+ 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 Stream? DownloadStream(RestRequest request, CancellationToken cancellationToken = default)
- => AsyncHelpers.RunSync(() => DownloadStreamAsync(request, cancellationToken));
+ 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
index 7c7b88cd2..25b265f09 100644
--- a/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs
+++ b/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs
@@ -14,7 +14,6 @@
//
using System.Net;
-using RestSharp.Extensions;
namespace RestSharp;
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index 0cdde6fa9..56631319e 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -3,7 +3,7 @@
true
false
- net472;net6.0
+ net472;net6.0;net7.0
disable
diff --git a/test/RestSharp.InteractiveTests/AuthenticationTests.cs b/test/RestSharp.InteractiveTests/AuthenticationTests.cs
index 859c70152..c9f9cd6d9 100644
--- a/test/RestSharp.InteractiveTests/AuthenticationTests.cs
+++ b/test/RestSharp.InteractiveTests/AuthenticationTests.cs
@@ -2,11 +2,11 @@
using System.Web;
using RestSharp.Authenticators;
-namespace RestSharp.InteractiveTests;
+namespace RestSharp.InteractiveTests;
public class AuthenticationTests {
public class TwitterKeys {
- public string ConsumerKey { get; set; }
+ public string ConsumerKey { get; set; }
public string ConsumerSecret { get; set; }
}
@@ -15,13 +15,15 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
var baseUrl = new Uri("https://api.twitter.com");
- var client = new RestClient(baseUrl) {
- Authenticator = OAuth1Authenticator.ForRequestToken(
- twitterKeys.ConsumerKey!,
- twitterKeys.ConsumerSecret,
- "https://restsharp.dev"
- )
- };
+ var client = new RestClient(
+ baseUrl,
+ options =>
+ options.Authenticator = OAuth1Authenticator.ForRequestToken(
+ twitterKeys.ConsumerKey!,
+ twitterKeys.ConsumerSecret,
+ "https://restsharp.dev"
+ )
+ );
var request = new RestRequest("oauth/request_token");
var response = await client.ExecuteAsync(request);
@@ -44,15 +46,16 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
Console.Write("Enter the verifier: ");
var verifier = Console.ReadLine();
- request = new RestRequest("oauth/access_token");
+ request = new RestRequest("oauth/access_token") {
+ Authenticator = OAuth1Authenticator.ForAccessToken(
+ twitterKeys.ConsumerKey!,
+ twitterKeys.ConsumerSecret,
+ oauthToken!,
+ oauthTokenSecret!,
+ verifier!
+ )
+ };
- client.Authenticator = OAuth1Authenticator.ForAccessToken(
- twitterKeys.ConsumerKey!,
- twitterKeys.ConsumerSecret,
- oauthToken!,
- oauthTokenSecret!,
- verifier!
- );
response = await client.ExecuteAsync(request);
Assert.NotNull(response);
@@ -65,16 +68,17 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter
Assert.NotNull(oauthToken);
Assert.NotNull(oauthTokenSecret);
- request = new RestRequest("1.1/account/verify_credentials.json");
+ request = new RestRequest("1.1/account/verify_credentials.json") {
+ Authenticator = OAuth1Authenticator.ForProtectedResource(
+ twitterKeys.ConsumerKey!,
+ twitterKeys.ConsumerSecret,
+ oauthToken!,
+ oauthTokenSecret!
+ )
+ };
- client.Authenticator = OAuth1Authenticator.ForProtectedResource(
- twitterKeys.ConsumerKey!,
- twitterKeys.ConsumerSecret,
- oauthToken!,
- oauthTokenSecret!
- );
response = await client.ExecuteAsync(request);
Console.WriteLine($"Code: {response.StatusCode}, response: {response.Content}");
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.InteractiveTests/TwitterClient.cs b/test/RestSharp.InteractiveTests/TwitterClient.cs
index e3290ceaa..480581a26 100644
--- a/test/RestSharp.InteractiveTests/TwitterClient.cs
+++ b/test/RestSharp.InteractiveTests/TwitterClient.cs
@@ -27,11 +27,10 @@ public class TwitterClient : ITwitterClient, IDisposable {
readonly RestClient _client;
public TwitterClient(string apiKey, string apiKeySecret) {
- var options = new RestClientOptions("https://api.twitter.com/2");
-
- _client = new RestClient(options) {
+ var options = new RestClientOptions("https://api.twitter.com/2") {
Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret)
};
+ _client = new RestClient(options);
}
public async Task GetUser(string user) {
@@ -93,12 +92,12 @@ protected override async ValueTask GetAuthenticationParameter(string
}
async Task GetToken() {
- var options = new RestClientOptions(_baseUrl);
-
- using var client = new RestClient(options) {
+ var options = new RestClientOptions(_baseUrl) {
Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret),
};
+ using var client = new RestClient(options);
+
var request = new RestRequest("oauth2/token")
.AddParameter("grant_type", "client_credentials");
var response = await client.PostAsync(request);
@@ -119,4 +118,4 @@ public record AddStreamSearchRule(string Value, string Tag);
public record SearchRulesResponse(string Value, string Tag, string Id);
-public record SearchResponse(string Id, string Text);
\ No newline at end of file
+public record SearchResponse(string Id, string Text);
diff --git a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs
index 76b62f221..8c9f0081d 100644
--- a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs
+++ b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs
@@ -1,7 +1,6 @@
using System.Text;
using System.Web;
using RestSharp.Authenticators;
-using RestSharp.Tests.Integrated.Fixtures;
using RestSharp.Tests.Integrated.Server;
namespace RestSharp.Tests.Integrated.Authentication;
@@ -21,9 +20,10 @@ public async Task Can_Authenticate_With_Basic_Http_Auth() {
const string userName = "testuser";
const string password = "testpassword";
- var client = new RestClient(_fixture.Server.Url) {
- Authenticator = new HttpBasicAuthenticator(userName, password)
- };
+ var client = new RestClient(
+ _fixture.Server.Url,
+ o => o.Authenticator = new HttpBasicAuthenticator(userName, password)
+ );
var request = new RestRequest("headers");
var response = await client.GetAsync(request);
@@ -35,4 +35,4 @@ public async Task Can_Authenticate_With_Basic_Http_Auth() {
parts[0].Should().Be(userName);
parts[1].Should().Be(password);
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs b/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs
index a773fa2eb..78a1bf985 100644
--- a/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs
+++ b/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs
@@ -1,8 +1,7 @@
using RestSharp.Authenticators.OAuth2;
-using RestSharp.Tests.Integrated.Fixtures;
using RestSharp.Tests.Integrated.Server;
-namespace RestSharp.Tests.Integrated.Authentication;
+namespace RestSharp.Tests.Integrated.Authentication;
[Collection(nameof(TestServerCollection))]
public class OAuth2Tests {
@@ -12,9 +11,8 @@ public class OAuth2Tests {
[Fact]
public async Task ShouldHaveProperHeader() {
- var client = new RestClient(_fixture.Server.Url);
var auth = new OAuth2AuthorizationRequestHeaderAuthenticator("token", "Bearer");
- client.Authenticator = auth;
+ var client = new RestClient(_fixture.Server.Url, o => o.Authenticator = auth);
var response = await client.GetJsonAsync("headers");
var authHeader = response!.FirstOrDefault(x => x.Name == KnownHeaders.Authorization);
@@ -22,4 +20,4 @@ public async Task ShouldHaveProperHeader() {
authHeader.Should().NotBeNull();
authHeader!.Value.Should().Be("Bearer token");
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/CookieTests.cs b/test/RestSharp.Tests.Integrated/CookieTests.cs
new file mode 100644
index 000000000..fdbcb4013
--- /dev/null
+++ b/test/RestSharp.Tests.Integrated/CookieTests.cs
@@ -0,0 +1,68 @@
+using System.Net;
+using RestSharp.Tests.Integrated.Server;
+
+namespace RestSharp.Tests.Integrated;
+
+[Collection(nameof(TestServerCollection))]
+public class CookieTests {
+ readonly RestClient _client;
+ readonly string _host;
+
+ public CookieTests(TestServerFixture fixture) {
+ _client = new RestClient(fixture.Server.Url);
+ _host = _client.Options.BaseUrl!.Host;
+ }
+
+ [Fact]
+ public async Task Can_Perform_GET_Async_With_Request_Cookies() {
+ var request = new RestRequest("get-cookies") {
+ CookieContainer = new CookieContainer()
+ };
+ request.CookieContainer.Add(new Cookie("cookie", "value", null, _host));
+ request.CookieContainer.Add(new Cookie("cookie2", "value2", null, _host));
+ var response = await _client.ExecuteAsync(request);
+ response.Content.Should().Be("[\"cookie=value\",\"cookie2=value2\"]");
+ }
+
+ [Fact]
+ public async Task Can_Perform_GET_Async_With_Response_Cookies() {
+ var request = new RestRequest("set-cookies");
+ var response = await _client.ExecuteAsync(request);
+ response.Content.Should().Be("success");
+
+ AssertCookie("cookie1", "value1", x => x == DateTime.MinValue);
+ FindCookie("cookie2").Should().BeNull("Cookie 2 should vanish as the path will not match");
+ AssertCookie("cookie3", "value3", x => x > DateTime.Now);
+ AssertCookie("cookie4", "value4", x => x > DateTime.Now);
+ FindCookie("cookie5").Should().BeNull("Cookie 5 should vanish as the request is not SSL");
+ AssertCookie("cookie6", "value6", x => x == DateTime.MinValue, true);
+
+ Cookie? FindCookie(string name) =>response!.Cookies!.FirstOrDefault(p => p.Name == name);
+
+ void AssertCookie(string name, string value, Func checkExpiration, bool httpOnly = false) {
+ var c = FindCookie(name)!;
+ c.Value.Should().Be(value);
+ c.Path.Should().Be("/");
+ c.Domain.Should().Be(_host);
+ checkExpiration(c.Expires).Should().BeTrue();
+ c.HttpOnly.Should().Be(httpOnly);
+ }
+ }
+
+ [Fact]
+ public async Task GET_Async_With_Response_Cookies_Should_Not_Fail_With_Cookie_With_Empty_Domain() {
+ var request = new RestRequest("set-cookies");
+ var response = await _client.ExecuteAsync(request);
+ response.Content.Should().Be("success");
+
+ Cookie? notFoundCookie = FindCookie("cookie_empty_domain");
+ notFoundCookie.Should().BeNull();
+
+ HeaderParameter? emptyDomainCookieHeader = response.Headers!
+ .SingleOrDefault(h => h.Name == KnownHeaders.SetCookie && ((string)h.Value!).StartsWith("cookie_empty_domain"));
+ emptyDomainCookieHeader.Should().NotBeNull();
+ ((string)emptyDomainCookieHeader!.Value!).Should().Contain("domain=;");
+
+ Cookie? FindCookie(string name) => response!.Cookies!.FirstOrDefault(p => p.Name == name);
+ }
+}
diff --git a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs
index 593aa2e65..742717d88 100644
--- a/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs
+++ b/test/RestSharp.Tests.Integrated/DefaultParameterTests.cs
@@ -1,12 +1,11 @@
using System.Net;
-using RestSharp.Tests.Integrated.Fixtures;
using RestSharp.Tests.Integrated.Server;
using RestSharp.Tests.Shared.Fixtures;
namespace RestSharp.Tests.Integrated;
[Collection(nameof(TestServerCollection))]
-public class DefaultParameterTests : IDisposable {
+public sealed class DefaultParameterTests : IDisposable {
readonly TestServerFixture _fixture;
readonly ITestOutputHelper _testOutputHelper;
readonly SimpleServer _server;
@@ -26,7 +25,7 @@ public async Task Should_add_default_and_request_query_get_parameters() {
await client.GetAsync(request);
- var query = RequestHandler.Url.Query;
+ var query = RequestHandler.Url!.Query;
query.Should().Contain("foo=bar");
query.Should().Contain("foo1=bar1");
}
@@ -38,7 +37,7 @@ public async Task Should_add_default_and_request_url_get_parameters() {
await client.GetAsync(request);
- RequestHandler.Url.Segments.Should().BeEquivalentTo("/", "bar/", "bar1");
+ RequestHandler.Url!.Segments.Should().BeEquivalentTo("/", "bar/", "bar1");
}
[Fact]
@@ -50,7 +49,7 @@ public async Task Should_not_throw_exception_when_name_is_null() {
}
static class RequestHandler {
- public static Uri Url { get; private set; }
+ public static Uri? Url { get; private set; }
public static void Handle(HttpListenerContext context) {
Url = context.Request.Url;
diff --git a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs
index aa30a158e..c9ad6d229 100644
--- a/test/RestSharp.Tests.Integrated/DownloadFileTests.cs
+++ b/test/RestSharp.Tests.Integrated/DownloadFileTests.cs
@@ -17,7 +17,7 @@ void FileHandler(HttpListenerRequest request, HttpListenerResponse response) {
var pathToFile = Path.Combine(
_path,
Path.Combine(
- request.Url.Segments.Select(s => s.Replace("/", "")).ToArray()
+ request.Url!.Segments.Select(s => s.Replace("/", "")).ToArray()
)
);
@@ -35,11 +35,11 @@ public async Task AdvancedResponseWriter_without_ResponseWriter_reads_stream() {
var tag = string.Empty;
var rr = new RestRequest("Assets/Koala.jpg") {
- AdvancedResponseWriter = response => {
+ AdvancedResponseWriter = (response, request) => {
var buf = new byte[16];
response.Content.ReadAsStream().Read(buf, 0, buf.Length);
tag = Encoding.ASCII.GetString(buf, 6, 4);
- return new RestResponse();
+ return new RestResponse(request);
}
};
diff --git a/test/RestSharp.Tests.Integrated/Fixtures/CaptureFixture.cs b/test/RestSharp.Tests.Integrated/Fixtures/CaptureFixture.cs
index fe488b3a5..73211c908 100644
--- a/test/RestSharp.Tests.Integrated/Fixtures/CaptureFixture.cs
+++ b/test/RestSharp.Tests.Integrated/Fixtures/CaptureFixture.cs
@@ -11,7 +11,7 @@ protected class RequestHeadCapturer
{
public const string Resource = "Capture";
- public static NameValueCollection CapturedHeaders { get; set; }
+ public static NameValueCollection? CapturedHeaders { get; set; }
public static void Initialize() => CapturedHeaders = null;
diff --git a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs b/test/RestSharp.Tests.Integrated/OAuth1Tests.cs
index 504f9bf81..026acba22 100644
--- a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs
+++ b/test/RestSharp.Tests.Integrated/OAuth1Tests.cs
@@ -1,10 +1,8 @@
-using System.Diagnostics;
-using System.Net;
-using System.Xml.Serialization;
+using System.Xml.Serialization;
using RestSharp.Authenticators;
using RestSharp.Authenticators.OAuth;
-using RestSharp.Tests.Integrated.Models;
using RestSharp.Tests.Shared.Extensions;
+#pragma warning disable CS8618
namespace RestSharp.Tests.Integrated;
@@ -26,152 +24,6 @@ class QueueItem {
public int Position { get; set; }
}
- [Fact(Skip = "Needs Netflix token")]
- public async Task Can_Authenticate_Netflix_With_OAuth() {
- const string consumerKey = "";
- const string consumerSecret = "";
-
- var baseUrl = new Uri("http://api.netflix.com");
-
- var client = new RestClient(baseUrl) {
- Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret)
- };
- var request = new RestRequest("oauth/request_token");
- var response = await client.ExecuteAsync(request);
-
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var qs = new Uri(response.Content).ParseQuery();
- var oauthToken = qs["oauth_token"];
- var oauthTokenSecret = qs["oauth_token_secret"];
- var applicationName = qs["application_name"];
-
- Assert.NotNull(oauthToken);
- Assert.NotNull(oauthTokenSecret);
- Assert.NotNull(applicationName);
-
- var baseSslUrl = new Uri("https://api-user.netflix.com");
- var sslClient = new RestClient(baseSslUrl);
-
- request = new RestRequest("oauth/login");
- request.AddParameter("oauth_token", oauthToken);
- request.AddParameter("oauth_consumer_key", consumerKey);
- request.AddParameter("application_name", applicationName);
-
- var url = sslClient.BuildUri(request)
- .ToString();
-
- Process.Start(url);
-
- request = new RestRequest("oauth/access_token"); // <-- Breakpoint here, login to netflix
-
- client.Authenticator = OAuth1Authenticator.ForAccessToken(
- consumerKey,
- consumerSecret,
- oauthToken,
- oauthTokenSecret
- );
- response = await client.ExecuteAsync(request);
-
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- qs = new Uri(response.Content).ParseQuery();
- oauthToken = qs["oauth_token"];
- oauthTokenSecret = qs["oauth_token_secret"];
-
- var userId = qs["user_id"];
-
- Assert.NotNull(oauthToken);
- Assert.NotNull(oauthTokenSecret);
- Assert.NotNull(userId);
-
- client.Authenticator = OAuth1Authenticator.ForProtectedResource(
- consumerKey,
- consumerSecret,
- oauthToken,
- oauthTokenSecret
- );
- request = new RestRequest("users/{user_id}/queues/disc");
- request.AddUrlSegment("user_id", userId);
- request.AddParameter("max_results", "2");
-
- var queueResponse = await client.ExecuteAsync(request);
-
- Assert.NotNull(queueResponse);
- Assert.Equal(HttpStatusCode.OK, queueResponse.StatusCode);
- Assert.NotNull(queueResponse.Data);
- Assert.Equal(2, queueResponse.Data.Items.Count);
- }
-
- [Fact(Skip = "Provide your own consumer key/secret before running")]
- public async Task Can_Authenticate_LinkedIN_With_OAuth() {
- const string consumerKey = "TODO_CONSUMER_KEY_HERE";
- const string consumerSecret = "TODO_CONSUMER_SECRET_HERE";
-
- // request token
- var client = new RestClient("https://api.linkedin.com/uas/oauth") {
- Authenticator = OAuth1Authenticator.ForRequestToken(
- consumerKey,
- consumerSecret,
- "http://localhost"
- )
- };
- var requestTokenRequest = new RestRequest("requestToken");
- var requestTokenResponse = await client.ExecuteAsync(requestTokenRequest);
-
- Assert.NotNull(requestTokenResponse);
- Assert.Equal(HttpStatusCode.OK, requestTokenResponse.StatusCode);
-
- var requestTokenResponseParameters = new Uri(requestTokenResponse.Content).ParseQuery();
- var requestToken = requestTokenResponseParameters["oauth_token"];
- var requestSecret = requestTokenResponseParameters["oauth_token_secret"];
-
- Assert.NotNull(requestToken);
- Assert.NotNull(requestSecret);
-
- // redirect user
- requestTokenRequest = new RestRequest("authenticate?oauth_token=" + requestToken);
-
- var redirectUri = client.BuildUri(requestTokenRequest);
-
- Process.Start(redirectUri.ToString());
-
- const string requestUrl = "TODO: put browser URL here";
- // replace this via the debugger with the return url from LinkedIN. Simply copy it from the opened browser
-
- if (!Debugger.IsAttached)
- Debugger.Launch();
-
- Debugger.Break();
-
- // get the access token
- var requestTokenQueryParameters = new Uri(requestUrl).ParseQuery();
- var requestVerifier = requestTokenQueryParameters["oauth_verifier"];
-
- client.Authenticator = OAuth1Authenticator.ForAccessToken(
- consumerKey,
- consumerSecret,
- requestToken,
- requestSecret,
- requestVerifier
- );
-
- var requestAccessTokenRequest = new RestRequest("accessToken");
- var requestActionTokenResponse = await client.ExecuteAsync(requestAccessTokenRequest);
-
- Assert.NotNull(requestActionTokenResponse);
- Assert.Equal(HttpStatusCode.OK, requestActionTokenResponse.StatusCode);
-
- var requestActionTokenResponseParameters = new Uri(requestActionTokenResponse.Content).ParseQuery();
- var accessToken = requestActionTokenResponseParameters["oauth_token"];
- var accessSecret = requestActionTokenResponseParameters["oauth_token_secret"];
-
- Assert.NotNull(accessToken);
- Assert.NotNull(accessSecret);
- }
-
[Fact]
public void Can_Authenticate_OAuth1_With_Querystring_Parameters() {
const string consumerKey = "enterConsumerKeyHere";
@@ -199,161 +51,6 @@ public void Can_Authenticate_OAuth1_With_Querystring_Parameters() {
actual.Should().BeEquivalentTo(expected);
}
- [Fact(Skip = "Provide your own consumer key/secret before running")]
- public async Task Can_Authenticate_Twitter() {
- var config = new {
- ConsumerKey = "",
- ConsumerSecret = "",
- AccessToken = "",
- AccessSecret = ""
- };
-
- var client = new RestClient("https://api.twitter.com/1.1") {
- Authenticator = OAuth1Authenticator.ForProtectedResource(
- config.ConsumerKey,
- config.ConsumerSecret,
- config.AccessToken,
- config.AccessSecret
- )
- };
-
- var request = new RestRequest("account/verify_credentials.json");
-
- request.AddParameter("include_entities", "true", ParameterType.QueryString);
-
- var response = await client.ExecuteAsync(request);
-
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- [Fact(Skip = "Provide your own consumer key/secret before running")]
- public async Task Can_Authenticate_With_OAuth() {
- const string consumerKey = "";
- const string consumerSecret = "";
-
- var baseUrl = new Uri("https://api.twitter.com");
-
- var client = new RestClient(baseUrl) {
- Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret)
- };
- var request = new RestRequest("oauth/request_token", Method.Post);
- var response = await client.ExecuteAsync(request);
-
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var qs = new Uri(response.Content).ParseQuery();
- var oauthToken = qs["oauth_token"];
- var oauthTokenSecret = qs["oauth_token_secret"];
-
- Assert.NotNull(oauthToken);
- Assert.NotNull(oauthTokenSecret);
-
- request = new RestRequest("oauth/authorize");
- request.AddParameter("oauth_token", oauthToken);
-
- var url = client.BuildUri(request).ToString();
-
- // Breakpoint here, open the URL from the url var in the browser
- // then set verifier in debugger to the value in the URL where you get redirected
- var verifier = "123456";
-
- request = new RestRequest("oauth/access_token", Method.Post);
-
- client.Authenticator = OAuth1Authenticator.ForAccessToken(
- consumerKey,
- consumerSecret,
- oauthToken,
- oauthTokenSecret,
- verifier
- );
- response = await client.ExecuteAsync(request);
-
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- qs = new Uri(response.Content).ParseQuery();
- oauthToken = qs["oauth_token"];
- oauthTokenSecret = qs["oauth_token_secret"];
-
- Assert.NotNull(oauthToken);
- Assert.NotNull(oauthTokenSecret);
-
- request = new RestRequest("/1.1/account/verify_credentials.json");
-
- client.Authenticator = OAuth1Authenticator.ForProtectedResource(
- consumerKey,
- consumerSecret,
- oauthToken,
- oauthTokenSecret
- );
-
- response = await client.ExecuteAsync(request);
-
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- [Fact(Skip = "Provide your own consumer key/secret before running")]
- public async Task Can_Query_Vimeo() {
- const string consumerKey = "TODO_CONSUMER_KEY_HERE";
- const string consumerSecret = "TODO_CONSUMER_SECRET_HERE";
-
- // arrange
- var client = new RestClient("http://vimeo.com/api/rest/v2") {
- Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret)
- };
- var request = new RestRequest();
-
- request.AddParameter("format", "json");
- request.AddParameter("method", "vimeo.videos.search");
- request.AddParameter("query", "weather");
- request.AddParameter("full_response", 1);
-
- // act
- var response = await client.ExecuteAsync(request);
-
- // assert
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- response.Content.Should().NotContain("\"stat\":\"fail\"");
- response.Content.Should().Contain("\"stat\":\"ok\"");
- }
-
- [Fact(Skip =
- "Provide your own consumer key/secret/accessToken/accessSecret before running. You can retrieve the access token/secret by running the LinkedIN oAuth test"
- )]
- public async Task Can_Retrieve_Member_Profile_Field_Field_Selector_From_LinkedIN() {
- const string consumerKey = "TODO_CONSUMER_KEY_HERE";
- const string consumerSecret = "TODO_CONSUMER_SECRET_HERE";
- const string accessToken = "TODO_ACCES_TOKEN_HERE";
- const string accessSecret = "TODO_ACCES_SECRET_HERE";
-
- // arrange
- var client = new RestClient("http://api.linkedin.com/v1") {
- Authenticator = OAuth1Authenticator.ForProtectedResource(
- consumerKey,
- consumerSecret,
- accessToken,
- accessSecret
- )
- };
- var request = new RestRequest("people/~:(id,first-name,last-name)");
-
- // act
- var response = await client.ExecuteAsync(request);
-
- // assert
- Assert.NotNull(response);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Data);
- Assert.NotNull(response.Data.Id);
- Assert.NotNull(response.Data.FirstName);
- Assert.NotNull(response.Data.LastName);
- }
-
[Fact]
public void Properly_Encodes_Parameter_Names() {
var postData = new WebPairCollection {
@@ -384,4 +81,4 @@ public void Use_RFC_3986_Encoding_For_Auth_Signature_Base() {
// assert
Assert.Equal("%3B%2F%3F%3A%40%26%3D%2B%24%2C%2521%252A%2527%2528%2529", escapedString);
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs
index 3033a3607..634c9d9e3 100644
--- a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs
+++ b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs
@@ -1,5 +1,4 @@
using System.Net;
-using RestSharp.Tests.Integrated.Fixtures;
using RestSharp.Tests.Integrated.Server;
namespace RestSharp.Tests.Integrated;
diff --git a/test/RestSharp.Tests.Integrated/RequestTests.cs b/test/RestSharp.Tests.Integrated/RequestTests.cs
index b1fd86041..312059004 100644
--- a/test/RestSharp.Tests.Integrated/RequestTests.cs
+++ b/test/RestSharp.Tests.Integrated/RequestTests.cs
@@ -1,5 +1,4 @@
using System.Net;
-using RestSharp.Tests.Integrated.Fixtures;
using RestSharp.Tests.Integrated.Server;
namespace RestSharp.Tests.Integrated;
@@ -8,10 +7,12 @@ namespace RestSharp.Tests.Integrated;
public class AsyncTests {
readonly ITestOutputHelper _output;
readonly RestClient _client;
+ readonly string _host;
public AsyncTests(TestServerFixture fixture, ITestOutputHelper output) {
- _output = output;
- _client = new RestClient(fixture.Server.Url);
+ _output = output;
+ _client = new RestClient(fixture.Server.Url);
+ _host = _client.Options.BaseUrl!.Host;
}
class Response {
@@ -51,63 +52,6 @@ public async Task Can_Perform_GET_Async() {
response.Content.Should().Be(val);
}
- [Fact]
- public async Task Can_Perform_GET_Async_With_Request_Cookies() {
- var request = new RestRequest("get-cookies") {
- CookieContainer = new CookieContainer()
- };
- request.CookieContainer.Add(new Cookie("cookie", "value", null, _client.Options.BaseUrl.Host));
- request.CookieContainer.Add(new Cookie("cookie2", "value2", null, _client.Options.BaseUrl.Host));
- var response = await _client.ExecuteAsync(request);
- response.Content.Should().Be("[\"cookie=value\",\"cookie2=value2\"]");
- }
-
- [Fact]
- public async Task Can_Perform_GET_Async_With_Response_Cookies() {
- var request = new RestRequest("set-cookies");
- var response = await _client.ExecuteAsync(request);
- response.Content.Should().Be("success");
-
- // Check we got all our cookies
- var domain = _client.Options.BaseUrl.Host;
- var cookie = response.Cookies!.First(p => p.Name == "cookie1");
- Assert.Equal("value1", cookie.Value);
- Assert.Equal("/", cookie.Path);
- Assert.Equal(domain, cookie.Domain);
- Assert.Equal(DateTime.MinValue, cookie.Expires);
- Assert.False(cookie.HttpOnly);
-
- // Cookie 2 should vanish as the path will not match
- cookie = response.Cookies!.FirstOrDefault(p => p.Name == "cookie2");
- Assert.Null(cookie);
-
- // Check cookie3 has a valid expiration
- cookie = response.Cookies!.First(p => p.Name == "cookie3");
- Assert.Equal("value3", cookie.Value);
- Assert.Equal("/", cookie.Path);
- Assert.Equal(domain, cookie.Domain);
- Assert.True(cookie.Expires > DateTime.Now);
-
- // Check cookie4 has a valid expiration
- cookie = response.Cookies!.First(p => p.Name == "cookie4");
- Assert.Equal("value4", cookie.Value);
- Assert.Equal("/", cookie.Path);
- Assert.Equal(domain, cookie.Domain);
- Assert.True(cookie.Expires > DateTime.Now);
-
- // Cookie 5 should vanish as the request is not SSL
- cookie = response.Cookies!.FirstOrDefault(p => p.Name == "cookie5");
- Assert.Null(cookie);
-
- // Check cookie6 should be http only
- cookie = response.Cookies!.First(p => p.Name == "cookie6");
- Assert.Equal("value6", cookie.Value);
- Assert.Equal("/", cookie.Path);
- Assert.Equal(domain, cookie.Domain);
- Assert.Equal(DateTime.MinValue, cookie.Expires);
- Assert.True(cookie.HttpOnly);
- }
-
[Fact]
public async Task Can_Timeout_GET_Async() {
var request = new RestRequest("timeout").AddBody("Body_Content");
@@ -136,4 +80,4 @@ public async Task Can_Delete_With_Response_Type_using_extension() {
response!.Message.Should().Be("Works!");
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj
index d1328674c..f084e1ecb 100644
--- a/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj
+++ b/test/RestSharp.Tests.Integrated/RestSharp.Tests.Integrated.csproj
@@ -1,26 +1,26 @@
- disable
- net6
+ enable
+ net6.0;net7.0
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/test/RestSharp.Tests.Integrated/RootElementTests.cs b/test/RestSharp.Tests.Integrated/RootElementTests.cs
index 3aa68abb6..608bf5238 100644
--- a/test/RestSharp.Tests.Integrated/RootElementTests.cs
+++ b/test/RestSharp.Tests.Integrated/RootElementTests.cs
@@ -10,7 +10,7 @@ public class RootElementTests {
public async Task Copy_RootElement_From_Request_To_IWithRootElement_Deserializer() {
using var server = HttpServerFixture.StartServer("success", Handle);
- var client = new RestClient(server.Url).UseXmlSerializer();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseXmlSerializer());
var request = new RestRequest("success") { RootElement = "Success" };
@@ -21,7 +21,7 @@ public async Task Copy_RootElement_From_Request_To_IWithRootElement_Deserializer
static void Handle(HttpListenerRequest req, HttpListenerResponse response) {
response.StatusCode = 200;
- response.Headers.Add(KnownHeaders.ContentType, Serializers.ContentType.Xml);
+ response.Headers.Add(KnownHeaders.ContentType, ContentType.Xml);
response.OutputStream.WriteStringUtf8(
@"
diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs
new file mode 100644
index 000000000..8dd64962a
--- /dev/null
+++ b/test/RestSharp.Tests.Integrated/Server/Handlers/CookieHandlers.cs
@@ -0,0 +1,70 @@
+using Microsoft.AspNetCore.Http;
+
+namespace RestSharp.Tests.Integrated.Server.Handlers;
+
+public static class CookieHandlers {
+ public static IResult HandleCookies(HttpContext ctx) {
+ var results = new List();
+
+ foreach (var (key, value) in ctx.Request.Cookies) {
+ results.Add($"{key}={value}");
+ }
+
+ return Results.Ok(results);
+ }
+
+ public static IResult HandleSetCookies(HttpContext ctx) {
+ ctx.Response.Cookies.Append("cookie1", "value1");
+
+ ctx.Response.Cookies.Append(
+ "cookie2",
+ "value2",
+ new CookieOptions {
+ Path = "/path_extra"
+ }
+ );
+
+ ctx.Response.Cookies.Append(
+ "cookie3",
+ "value3",
+ new CookieOptions {
+ Expires = DateTimeOffset.Now.AddDays(2)
+ }
+ );
+
+ ctx.Response.Cookies.Append(
+ "cookie4",
+ "value4",
+ new CookieOptions {
+ MaxAge = TimeSpan.FromSeconds(100)
+ }
+ );
+
+ ctx.Response.Cookies.Append(
+ "cookie5",
+ "value5",
+ new CookieOptions {
+ Secure = true
+ }
+ );
+
+ ctx.Response.Cookies.Append(
+ "cookie6",
+ "value6",
+ new CookieOptions {
+ HttpOnly = true
+ }
+ );
+
+ ctx.Response.Cookies.Append(
+ "cookie_empty_domain",
+ "value_empty_domain",
+ new CookieOptions {
+ HttpOnly = true,
+ Domain = string.Empty
+ }
+ );
+
+ return Results.Content("success");
+ }
+}
diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs
new file mode 100644
index 000000000..d65f843b0
--- /dev/null
+++ b/test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs
@@ -0,0 +1,49 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using RestSharp.Extensions;
+
+namespace RestSharp.Tests.Integrated.Server.Handlers;
+
+public static class FileHandlers {
+ public static async Task HandleUpload(string assetPath, HttpRequest req) {
+ if (!req.HasFormContentType) {
+ return Results.BadRequest("It's not a form");
+ }
+
+ var form = await req.ReadFormAsync();
+ var file = form.Files["file"];
+
+ if (file is null) {
+ return Results.BadRequest("File parameter 'file' is not present");
+ }
+
+ await using var stream = file.OpenReadStream();
+
+ var received = await stream.ReadAsBytes(default);
+ var expected = await File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName));
+
+ var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected));
+ return Results.Json(response);
+ }
+}
+
+[ApiController]
+public class UploadController : ControllerBase {
+ [HttpPost]
+ [Route("upload")]
+ public async Task Upload([FromForm] FormFile formFile) {
+ var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
+ var file = formFile.File;
+ await using var stream = file.OpenReadStream();
+
+ var received = await stream.ReadAsBytes(default);
+ var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName));
+
+ var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected));
+ return response;
+ }
+}
+
+public class FormFile {
+ public IFormFile File { get; set; }
+}
diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs
new file mode 100644
index 000000000..5339052a8
--- /dev/null
+++ b/test/RestSharp.Tests.Integrated/Server/Handlers/HeaderHandlers.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Http;
+
+namespace RestSharp.Tests.Integrated.Server.Handlers;
+
+public static class HeaderHandlers {
+ public static IResult HandleHeaders(HttpContext ctx) {
+ var response = ctx.Request.Headers.Select(x => new TestServerResponse(x.Key, x.Value));
+ return Results.Ok(response);
+ }
+}
diff --git a/test/RestSharp.Tests.Integrated/Server/Handlers/RequestHandlers.cs b/test/RestSharp.Tests.Integrated/Server/Handlers/RequestHandlers.cs
new file mode 100644
index 000000000..43cda8d30
--- /dev/null
+++ b/test/RestSharp.Tests.Integrated/Server/Handlers/RequestHandlers.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNetCore.Http;
+
+namespace RestSharp.Tests.Integrated.Server.Handlers;
+
+public static class RequestHandlers {
+ public static IResult ParseRequest(HttpContext ctx) => Results.Ok(new ParsedRequest(ctx.Request));
+}
+
+public class ParsedRequest {
+ public ParsedRequest(HttpRequest request) {
+ Method = request.Method;
+ Path = request.Path;
+ QueryString = request.QueryString;
+
+ QueryParameters = request.Query
+ .SelectMany(x => x.Value.Select(y => new KeyValuePair(x.Key, y)))
+ .ToArray();
+ }
+
+ public string Method { get; set; }
+ public string Path { get; set; }
+ public QueryString QueryString { get; set; }
+ public KeyValuePair[] QueryParameters { get; set; }
+}
diff --git a/test/RestSharp.Tests.Integrated/Server/Models.cs b/test/RestSharp.Tests.Integrated/Server/Models.cs
index 418f7a191..9bf592861 100644
--- a/test/RestSharp.Tests.Integrated/Server/Models.cs
+++ b/test/RestSharp.Tests.Integrated/Server/Models.cs
@@ -6,6 +6,6 @@ record TestServerResponse(string Name, string Value);
record UploadRequest(string Filename, IFormFile File);
-record UploadResponse(string FileName, long Length, bool Equal);
+public record UploadResponse(string FileName, long Length, bool Equal);
record ContentResponse(string Content);
diff --git a/test/RestSharp.Tests.Integrated/Server/TestServer.cs b/test/RestSharp.Tests.Integrated/Server/TestServer.cs
index 7570bdd66..df9307e07 100644
--- a/test/RestSharp.Tests.Integrated/Server/TestServer.cs
+++ b/test/RestSharp.Tests.Integrated/Server/TestServer.cs
@@ -1,11 +1,11 @@
-using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using RestSharp.Extensions;
+using RestSharp.Tests.Integrated.Server.Handlers;
using RestSharp.Tests.Shared.Extensions;
+// ReSharper disable ConvertClosureToMethodGroup
namespace RestSharp.Tests.Integrated.Server;
@@ -17,29 +17,29 @@ public sealed class HttpServer {
public const string ContentResource = "content";
public const string TimeoutResource = "timeout";
- public HttpServer(ITestOutputHelper output = null) {
+ public HttpServer(ITestOutputHelper? output = null) {
var builder = WebApplication.CreateBuilder();
- if (output != null)
- builder.WebHost.ConfigureLogging(x => x.SetMinimumLevel(LogLevel.Information).AddXunit(output, LogLevel.Debug));
-
+ if (output != null) builder.WebHost.ConfigureLogging(x => x.SetMinimumLevel(LogLevel.Information).AddXunit(output, LogLevel.Debug));
+
+ builder.Services.AddControllers().AddApplicationPart(typeof(UploadController).Assembly);
builder.WebHost.UseUrls(Address);
_app = builder.Build();
- // GET
+ _app.MapControllers();
+
_app.MapGet("success", () => new TestResponse { Message = "Works!" });
_app.MapGet("echo", (string msg) => msg);
_app.MapGet(TimeoutResource, async () => await Task.Delay(2000));
_app.MapPut(TimeoutResource, async () => await Task.Delay(2000));
- // ReSharper disable once ConvertClosureToMethodGroup
_app.MapGet("status", (int code) => Results.StatusCode(code));
- _app.MapGet("headers", HandleHeaders);
+ _app.MapGet("headers", HeaderHandlers.HandleHeaders);
_app.MapGet("request-echo", async context => await context.Request.BodyReader.AsStream().CopyToAsync(context.Response.BodyWriter.AsStream()));
_app.MapDelete("delete", () => new TestResponse { Message = "Works!" });
// Cookies
- _app.MapGet("get-cookies", HandleCookies);
- _app.MapGet("set-cookies", HandleSetCookies);
+ _app.MapGet("get-cookies", CookieHandlers.HandleCookies);
+ _app.MapGet("set-cookies", CookieHandlers.HandleSetCookies);
// PUT
_app.MapPut(
@@ -51,66 +51,16 @@ public HttpServer(ITestOutputHelper output = null) {
);
// Upload file
- var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
+ // var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
+ // _app.MapPost("/upload", ctx => FileHandlers.HandleUpload(assetPath, ctx.Request));
- _app.MapPost("/upload", HandleUpload);
-
// POST
_app.MapPost("/post/json", (TestRequest request) => new TestResponse { Message = request.Data });
- _app.MapPost("/post/form", (HttpContext context) => new TestResponse { Message = $"Works! Length: {context.Request.Form["big_string"].ToString().Length}" });
-
- IResult HandleHeaders(HttpContext ctx) {
- var response = ctx.Request.Headers.Select(x => new TestServerResponse(x.Key, x.Value));
- return Results.Ok(response);
- }
-
- IResult HandleCookies(HttpContext ctx) {
- var results = new List();
- foreach (var (key, value) in ctx.Request.Cookies) {
- results.Add($"{key}={value}");
- }
- return Results.Ok(results);
- }
-
- IResult HandleSetCookies(HttpContext ctx) {
- ctx.Response.Cookies.Append("cookie1", "value1");
- ctx.Response.Cookies.Append("cookie2", "value2", new CookieOptions {
- Path = "/path_extra"
- });
- ctx.Response.Cookies.Append("cookie3", "value3", new CookieOptions {
- Expires = DateTimeOffset.Now.AddDays(2)
- });
- ctx.Response.Cookies.Append("cookie4", "value4", new CookieOptions {
- MaxAge = TimeSpan.FromSeconds(100)
- });
- ctx.Response.Cookies.Append("cookie5", "value5", new CookieOptions {
- Secure = true
- });
- ctx.Response.Cookies.Append("cookie6", "value6", new CookieOptions {
- HttpOnly = true
- });
- return Results.Content("success");
- }
-
- async Task HandleUpload(HttpRequest req) {
- if (!req.HasFormContentType) {
- return Results.BadRequest("It's not a form");
- }
- var form = await req.ReadFormAsync();
- var file = form.Files["file"];
-
- if (file is null) {
- return Results.BadRequest("File parameter 'file' is not present");
- }
-
- await using var stream = file.OpenReadStream();
-
- var received = await stream.ReadAsBytes(default);
- var expected = await File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName));
-
- return Results.Ok(new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected)));
- }
+ _app.MapPost(
+ "/post/form",
+ (HttpContext context) => new TestResponse { Message = $"Works! Length: {context.Request.Form["big_string"].ToString().Length}" }
+ );
}
public Uri Url => new(Address);
@@ -121,4 +71,4 @@ public async Task Stop() {
await _app.StopAsync();
await _app.DisposeAsync();
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs
index a8f4cd8b9..c9fbb2e77 100644
--- a/test/RestSharp.Tests.Integrated/StatusCodeTests.cs
+++ b/test/RestSharp.Tests.Integrated/StatusCodeTests.cs
@@ -4,13 +4,15 @@
using RestSharp.Tests.Shared.Extensions;
using RestSharp.Tests.Shared.Fixtures;
+// ReSharper disable UnusedMember.Local
+// ReSharper disable InconsistentNaming
+
namespace RestSharp.Tests.Integrated;
public class StatusCodeTests : IDisposable {
public StatusCodeTests() {
_server = SimpleServer.Create(UrlToStatusCodeHandler);
- _client = new RestClient(_server.Url);
- _client.UseXmlSerializer();
+ _client = new RestClient(_server.Url, configureSerialization: cfg => cfg.UseXmlSerializer());
}
public void Dispose() => _server.Dispose();
@@ -18,7 +20,7 @@ public StatusCodeTests() {
readonly SimpleServer _server;
readonly RestClient _client;
- static void UrlToStatusCodeHandler(HttpListenerContext obj) => obj.Response.StatusCode = int.Parse(obj.Request.Url.Segments.Last());
+ static void UrlToStatusCodeHandler(HttpListenerContext obj) => obj.Response.StatusCode = int.Parse(obj.Request.Url!.Segments.Last());
[Fact]
public async Task ContentType_Additional_Information() {
@@ -63,7 +65,7 @@ public async Task Handles_Different_Root_Element_On_Http_Error() {
_server.SetHandler(Handlers.Generic());
var request = new RestRequest("error") {
- RootElement = "Success",
+ RootElement = "Success",
OnBeforeDeserialization = resp => {
if (resp.StatusCode == HttpStatusCode.BadRequest) resp.RootElement = "Error";
}
@@ -177,4 +179,4 @@ void success(HttpListenerContext context)
public class TestResponse {
public string Message { get; set; }
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs
index 3e2d93279..7c1617c00 100644
--- a/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs
+++ b/test/RestSharp.Tests.Integrated/StructuredSyntaxSuffixTests.cs
@@ -3,14 +3,16 @@
using RestSharp.Tests.Shared.Extensions;
using RestSharp.Tests.Shared.Fixtures;
-namespace RestSharp.Tests.Integrated;
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+
+namespace RestSharp.Tests.Integrated;
public class StructuredSyntaxSuffixTests : IDisposable {
readonly TestHttpServer _server;
readonly string _url;
class Person {
- public string Name { get; set; }
+ public string Name { get; set; } = null!;
public int Age { get; set; }
}
@@ -41,8 +43,8 @@ public async Task By_default_application_json_content_type_should_deserialize_as
var response = await client.ExecuteAsync(request);
- Assert.Equal("Bob", response.Data.Name);
- Assert.Equal(50, response.Data.Age);
+ response.Data!.Name.Should().Be("Bob");
+ response.Data.Age.Should().Be(50);
}
[Fact]
@@ -61,21 +63,21 @@ public async Task By_default_content_types_with_JSON_structured_syntax_suffix_sh
[Fact]
public async Task By_default_content_types_with_XML_structured_syntax_suffix_should_deserialize_as_XML() {
- var client = new RestClient(_url).UseXmlSerializer();
+ var client = new RestClient(_url, configureSerialization: cfg => cfg.UseXmlSerializer());
var request = new RestRequest()
.AddParameter("ct", "application/vnd.somebody.something+xml")
.AddParameter("c", XmlContent);
var response = await client.ExecuteAsync(request);
-
+
response.Data!.Name.Should().Be("Bob");
response.Data.Age.Should().Be(50);
}
[Fact]
public async Task By_default_text_xml_content_type_should_deserialize_as_XML() {
- var client = new RestClient(_url).UseXmlSerializer();
+ var client = new RestClient(_url, configureSerialization: cfg => cfg.UseXmlSerializer());
var request = new RestRequest()
.AddParameter("ct", "text/xml")
@@ -83,7 +85,7 @@ public async Task By_default_text_xml_content_type_should_deserialize_as_XML() {
var response = await client.ExecuteAsync(request);
- Assert.Equal("Bob", response.Data.Name);
+ Assert.Equal("Bob", response.Data!.Name);
Assert.Equal(50, response.Data.Age);
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Integrated/UploadFileTests.cs b/test/RestSharp.Tests.Integrated/UploadFileTests.cs
index 2826a7174..843e9048e 100644
--- a/test/RestSharp.Tests.Integrated/UploadFileTests.cs
+++ b/test/RestSharp.Tests.Integrated/UploadFileTests.cs
@@ -1,13 +1,18 @@
+using System.Net;
using RestSharp.Tests.Integrated.Server;
namespace RestSharp.Tests.Integrated;
[Collection(nameof(TestServerCollection))]
public class UploadFileTests {
- readonly RestClient _client;
- readonly string _path = AppDomain.CurrentDomain.BaseDirectory;
+ readonly ITestOutputHelper _output;
+ readonly RestClient _client;
+ readonly string _path = AppDomain.CurrentDomain.BaseDirectory;
- public UploadFileTests(TestServerFixture fixture) => _client = new RestClient(fixture.Server.Url);
+ public UploadFileTests(TestServerFixture fixture, ITestOutputHelper output) {
+ _output = output;
+ _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true });
+ }
[Fact]
public async Task Should_upload_from_file() {
@@ -16,11 +21,14 @@ public async Task Should_upload_from_file() {
var path = Path.Combine(_path, "Assets", filename);
var request = new RestRequest("upload").AddFile("file", path);
- var response = await _client.PostAsync(request);
+ var response = await _client.ExecutePostAsync(request);
+
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
var expected = new UploadResponse(filename, new FileInfo(path).Length, true);
- response.Should().BeEquivalentTo(expected);
+ _output.WriteLine(response.Content);
+ response.Data.Should().BeEquivalentTo(expected);
}
[Fact]
@@ -31,11 +39,12 @@ public async Task Should_upload_from_bytes() {
var bytes = await File.ReadAllBytesAsync(path);
var request = new RestRequest("upload").AddFile("file", bytes, filename);
- var response = await _client.PostAsync(request);
+ var response = await _client.ExecutePostAsync(request);
var expected = new UploadResponse(filename, new FileInfo(path).Length, true);
- response.Should().BeEquivalentTo(expected);
+ _output.WriteLine(response.Content);
+ response.Data.Should().BeEquivalentTo(expected);
}
[Fact]
@@ -45,10 +54,11 @@ public async Task Should_upload_from_stream() {
var path = Path.Combine(_path, "Assets", filename);
var request = new RestRequest("upload").AddFile("file", () => File.OpenRead(path), filename);
- var response = await _client.PostAsync(request);
+ var response = await _client.ExecutePostAsync(request);
var expected = new UploadResponse(filename, new FileInfo(path).Length, true);
- response.Should().BeEquivalentTo(expected);
+ _output.WriteLine(response.Content);
+ response.Data.Should().BeEquivalentTo(expected);
}
}
diff --git a/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs b/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs
index 67bdcc1ad..5901d14d1 100644
--- a/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs
+++ b/test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs
@@ -35,7 +35,7 @@ public async Task Use_CsvHelper_For_Response() {
}
);
- var client = new RestClient(server.Url).UseCsvHelper();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseCsvHelper());
var actual = await client.GetAsync(new RestRequest());
@@ -72,7 +72,7 @@ public async Task Use_CsvHelper_For_Collection_Response() {
}
);
- var client = new RestClient(server.Url).UseCsvHelper();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseCsvHelper());
var actual = await client.GetAsync>(new RestRequest());
@@ -90,7 +90,7 @@ public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse() {
}
);
- var client = new RestClient(server.Url).UseCsvHelper();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseCsvHelper());
var response = await client.ExecuteAsync(new RestRequest());
@@ -99,7 +99,7 @@ public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse() {
}
[Fact]
- public async Task DeserilizationSucceeds_IsSuccessfull_Should_BeTrue() {
+ public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() {
var item = Fixture.Create();
using var server = HttpServerFixture.StartServer(
@@ -113,7 +113,7 @@ public async Task DeserilizationSucceeds_IsSuccessfull_Should_BeTrue() {
}
);
- var client = new RestClient(server.Url).UseSystemTextJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson());
var response = await client.ExecuteAsync(new RestRequest());
diff --git a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJsonTests.cs b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJsonTests.cs
index f1bb1f696..7702a10e0 100644
--- a/test/RestSharp.Tests.Serializers.Json/NewtonsoftJsonTests.cs
+++ b/test/RestSharp.Tests.Serializers.Json/NewtonsoftJsonTests.cs
@@ -6,7 +6,7 @@
using RestSharp.Tests.Shared.Extensions;
using RestSharp.Tests.Shared.Fixtures;
-namespace RestSharp.Tests.Serializers.Json;
+namespace RestSharp.Tests.Serializers.Json;
public class NewtonsoftJsonTests {
static readonly Fixture Fixture = new();
@@ -66,12 +66,12 @@ public async Task Use_JsonNet_For_Requests() {
var testData = Fixture.Create();
- var client = new RestClient(server.Url).UseNewtonsoftJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson());
var request = new RestRequest().AddJsonBody(testData);
await client.PostAsync(request);
- var actual = serializer.Deserialize(new RestResponse { Content = _body! });
+ var actual = serializer.Deserialize(new RestResponse(request) { Content = _body! });
actual.Should().BeEquivalentTo(testData);
}
@@ -90,7 +90,7 @@ public async Task Use_JsonNet_For_Response() {
}
);
- var client = new RestClient(server.Url).UseNewtonsoftJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson());
var actual = await client.GetAsync(new RestRequest());
@@ -98,18 +98,17 @@ public async Task Use_JsonNet_For_Response() {
}
[Fact]
- public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse()
- {
+ public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() {
using var server = HttpServerFixture.StartServer(
(_, response) => {
- response.StatusCode = (int)HttpStatusCode.OK;
- response.ContentType = "application/json";
+ response.StatusCode = (int)HttpStatusCode.OK;
+ response.ContentType = "application/json";
response.ContentEncoding = Encoding.UTF8;
response.OutputStream.WriteStringUtf8("invalid json");
}
);
- var client = new RestClient(server.Url).UseNewtonsoftJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson());
var response = await client.ExecuteAsync(new RestRequest());
@@ -118,25 +117,25 @@ public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse()
}
[Fact]
- public async Task DeserilizationSucceeds_IsSuccessfull_Should_BeTrue() {
+ public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() {
var item = Fixture.Create();
using var server = HttpServerFixture.StartServer(
(_, response) => {
var serializer = new JsonNetSerializer();
- response.StatusCode = (int)HttpStatusCode.OK;
- response.ContentType = "application/json";
+ response.StatusCode = (int)HttpStatusCode.OK;
+ response.ContentType = "application/json";
response.ContentEncoding = Encoding.UTF8;
response.OutputStream.WriteStringUtf8(serializer.Serialize(item)!);
}
);
- var client = new RestClient(server.Url).UseNewtonsoftJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseNewtonsoftJson());
var response = await client.ExecuteAsync(new RestRequest());
response.IsSuccessStatusCode.Should().BeTrue();
response.IsSuccessful.Should().BeTrue();
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Serializers.Json/SystemTextJsonTests.cs b/test/RestSharp.Tests.Serializers.Json/SystemTextJsonTests.cs
index 09e4aa60a..b61428234 100644
--- a/test/RestSharp.Tests.Serializers.Json/SystemTextJsonTests.cs
+++ b/test/RestSharp.Tests.Serializers.Json/SystemTextJsonTests.cs
@@ -4,7 +4,7 @@
using RestSharp.Tests.Shared.Extensions;
using RestSharp.Tests.Shared.Fixtures;
-namespace RestSharp.Tests.Serializers.Json;
+namespace RestSharp.Tests.Serializers.Json;
public class SystemTextJsonTests {
static readonly Fixture Fixture = new();
@@ -24,7 +24,7 @@ public async Task Use_JsonNet_For_Requests() {
await client.PostAsync(request);
- var actual = serializer.Deserialize(new RestResponse { Content = _body });
+ var actual = serializer.Deserialize(new RestResponse(request) { Content = _body });
actual.Should().BeEquivalentTo(testData);
@@ -45,7 +45,7 @@ public async Task Use_JsonNet_For_Response() {
}
);
- var client = new RestClient(server.Url).UseSystemTextJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson());
var actual = await client.GetAsync(new RestRequest());
@@ -53,17 +53,17 @@ public async Task Use_JsonNet_For_Response() {
}
[Fact]
- public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse() {
+ public async Task DeserilizationFails_IsSuccessful_Should_BeFalse() {
using var server = HttpServerFixture.StartServer(
(_, response) => {
- response.StatusCode = (int)HttpStatusCode.OK;
- response.ContentType = "application/json";
+ response.StatusCode = (int)HttpStatusCode.OK;
+ response.ContentType = "application/json";
response.ContentEncoding = Encoding.UTF8;
response.OutputStream.WriteStringUtf8("invalid json");
}
);
- var client = new RestClient(server.Url).UseSystemTextJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson());
var response = await client.ExecuteAsync(new RestRequest());
@@ -72,25 +72,25 @@ public async Task DeserilizationFails_IsSuccessfull_Should_BeFalse() {
}
[Fact]
- public async Task DeserilizationSucceeds_IsSuccessfull_Should_BeTrue() {
+ public async Task DeserilizationSucceeds_IsSuccessful_Should_BeTrue() {
var item = Fixture.Create();
using var server = HttpServerFixture.StartServer(
(_, response) => {
var serializer = new SystemTextJsonSerializer();
- response.StatusCode = (int)HttpStatusCode.OK;
- response.ContentType = "application/json";
+ response.StatusCode = (int)HttpStatusCode.OK;
+ response.ContentType = "application/json";
response.ContentEncoding = Encoding.UTF8;
response.OutputStream.WriteStringUtf8(serializer.Serialize(item)!);
}
);
- var client = new RestClient(server.Url).UseSystemTextJson();
+ var client = new RestClient(server.Url, configureSerialization: cfg => cfg.UseSystemTextJson());
var response = await client.ExecuteAsync(new RestRequest());
response.IsSuccessStatusCode.Should().BeTrue();
response.IsSuccessful.Should().BeTrue();
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs b/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs
index 692329fd5..6e20a4016 100644
--- a/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs
+++ b/test/RestSharp.Tests.Serializers.Xml/NamespacedXmlTests.cs
@@ -3,7 +3,7 @@
using RestSharp.Tests.Serializers.Xml.SampleClasses;
using RestSharp.Tests.Serializers.Xml.SampleClasses.DeserializeAsTest;
-namespace RestSharp.Tests.Serializers.Xml;
+namespace RestSharp.Tests.Serializers.Xml;
public class NamespacedXmlTests {
const string GuidString = "AC1FC4BC-087A-4242-B8EE-C53EBE9887A5";
@@ -25,22 +25,22 @@ static string CreateUnderscoresXml() {
var ns = XNamespace.Get("http://restsharp.org");
var root = new XElement(ns + "Person");
- root.Add(new XElement(ns + "Name", "John Sheehan"));
- root.Add(new XElement(ns + "Start_Date", new DateTime(2009, 9, 25, 0, 6, 1)));
+ root.Add(new XElement(ns + "Name", "John Sheehan"));
+ root.Add(new XElement(ns + "Start_Date", new DateTime(2009, 9, 25, 0, 6, 1)));
root.Add(new XAttribute(ns + "Age", 28));
- root.Add(new XElement(ns + "Percent", 99.9999m));
- root.Add(new XElement(ns + "Big_Number", long.MaxValue));
+ root.Add(new XElement(ns + "Percent", 99.9999m));
+ root.Add(new XElement(ns + "Big_Number", long.MaxValue));
root.Add(new XAttribute(ns + "Is_Cool", false));
- root.Add(new XElement(ns + "Ignore", "dummy"));
+ root.Add(new XElement(ns + "Ignore", "dummy"));
root.Add(new XAttribute(ns + "Read_Only", "dummy"));
root.Add(new XAttribute(ns + "Unique_Id", new Guid(GuidString)));
- root.Add(new XElement(ns + "Url", "http://example.com"));
- root.Add(new XElement(ns + "Url_Path", "/foo/bar"));
+ root.Add(new XElement(ns + "Url", "http://example.com"));
+ root.Add(new XElement(ns + "Url_Path", "/foo/bar"));
root.Add(
new XElement(
ns + "Best_Friend",
- new XElement(ns + "Name", "The Fonz"),
+ new XElement(ns + "Name", "The Fonz"),
new XAttribute(ns + "Since", 1952)
)
);
@@ -51,7 +51,7 @@ static string CreateUnderscoresXml() {
friends.Add(
new XElement(
ns + "Friend",
- new XElement(ns + "Name", "Friend" + i),
+ new XElement(ns + "Name", "Friend" + i),
new XAttribute(ns + "Since", DateTime.Now.Year - i)
)
);
@@ -101,7 +101,7 @@ static string CreateElementsXml() {
friends.Add(
new XElement(
ns + "Friend",
- new XElement(ns + "Name", "Friend" + i),
+ new XElement(ns + "Name", "Friend" + i),
new XElement(ns + "Since", DateTime.Now.Year - i)
)
);
@@ -153,7 +153,8 @@ static string CreateAttributesXml() {
[Fact]
public void Can_Deserialize_Attribute_Using_Exact_Name_Defined_In_DeserializeAs_Attribute() {
const string @namespace = "http://restsharp.org";
- var ns = XNamespace.Get(@namespace);
+
+ var ns = XNamespace.Get(@namespace);
var doc = new XDocument(
new XElement(
@@ -168,7 +169,7 @@ public void Can_Deserialize_Attribute_Using_Exact_Name_Defined_In_DeserializeAs_
};
var xml = new XmlDeserializer { Namespace = @namespace };
- var output = xml.Deserialize(new RestResponse { Content = doc.ToString() });
+ var output = xml.Deserialize(new RestResponse() { Content = doc.ToString() });
Assert.Equal(expected.AttributeValue, output.AttributeValue);
}
@@ -321,4 +322,4 @@ public void Ignore_ReadOnly_Property_That_Exists_In_Data() {
Assert.Null(p.ReadOnlyProxy);
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/Lastfm.cs b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/Lastfm.cs
index 6a5002234..1e7b872b7 100644
--- a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/Lastfm.cs
+++ b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/Lastfm.cs
@@ -1,4 +1,6 @@
-namespace RestSharp.Tests.Serializers.Xml.SampleClasses;
+// ReSharper disable InconsistentNaming
+#pragma warning disable CS8981
+namespace RestSharp.Tests.Serializers.Xml.SampleClasses;
public class Event : LastfmBase {
public string id { get; set; }
diff --git a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/ListSamples.cs b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/ListSamples.cs
index 5cb169682..d8b1491e9 100644
--- a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/ListSamples.cs
+++ b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/ListSamples.cs
@@ -1,4 +1,6 @@
-namespace RestSharp.Tests.Serializers.Xml.SampleClasses;
+// ReSharper disable InconsistentNaming
+#pragma warning disable CS8981
+namespace RestSharp.Tests.Serializers.Xml.SampleClasses;
public class SimpleTypesListSample {
public List Names { get; set; }
diff --git a/test/RestSharp.Tests/JwtAuthTests.cs b/test/RestSharp.Tests/JwtAuthTests.cs
index d82a5e137..f2c68ebdc 100644
--- a/test/RestSharp.Tests/JwtAuthTests.cs
+++ b/test/RestSharp.Tests/JwtAuthTests.cs
@@ -1,7 +1,7 @@
using System.Globalization;
using RestSharp.Authenticators;
-namespace RestSharp.Tests;
+namespace RestSharp.Tests;
public class JwtAuthTests {
readonly string _testJwt;
@@ -12,10 +12,10 @@ public JwtAuthTests() {
Thread.CurrentThread.CurrentUICulture = CultureInfo.InstalledUICulture;
_testJwt = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" +
- "." +
+ "." +
"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQo" +
"gImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" +
- "." +
+ "." +
"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
_expectedAuthHeaderContent = $"Bearer {_testJwt}";
@@ -23,25 +23,25 @@ public JwtAuthTests() {
[Fact]
public async Task Can_Set_ValidFormat_Auth_Header() {
- var client = new RestClient { Authenticator = new JwtAuthenticator(_testJwt) };
+ var client = new RestClient(new RestClientOptions { Authenticator = new JwtAuthenticator(_testJwt) });
var request = new RestRequest();
//In real case client.Execute(request) will invoke Authenticate method
- await client.Authenticator.Authenticate(client, request);
+ await client.Options.Authenticator!.Authenticate(client, request);
var authParam = request.Parameters.Single(p => p.Name.Equals(KnownHeaders.Authorization, StringComparison.OrdinalIgnoreCase));
Assert.True(authParam.Type == ParameterType.HttpHeader);
Assert.Equal(_expectedAuthHeaderContent, authParam.Value);
}
-
+
[Fact]
public async Task Can_Set_ValidFormat_Auth_Header_With_Bearer_Prefix() {
- var client = new RestClient { Authenticator = new JwtAuthenticator($"Bearer {_testJwt}") };
+ var client = new RestClient(new RestClientOptions { Authenticator = new JwtAuthenticator($"Bearer {_testJwt}") });
var request = new RestRequest();
//In real case client.Execute(request) will invoke Authenticate method
- await client.Authenticator.Authenticate(client, request);
+ await client.Options.Authenticator!.Authenticate(client, request);
var authParam = request.Parameters.Single(p => p.Name.Equals(KnownHeaders.Authorization, StringComparison.OrdinalIgnoreCase));
@@ -51,14 +51,14 @@ public async Task Can_Set_ValidFormat_Auth_Header_With_Bearer_Prefix() {
[Fact]
public async Task Check_Only_Header_Authorization() {
- var client = new RestClient { Authenticator = new JwtAuthenticator(_testJwt) };
+ var client = new RestClient(new RestClientOptions { Authenticator = new JwtAuthenticator(_testJwt) });
var request = new RestRequest();
// Paranoid server needs "two-factor authentication": jwt header and query param key for example
request.AddParameter(KnownHeaders.Authorization, "manualAuth", ParameterType.QueryString);
// In real case client.Execute(request) will invoke Authenticate method
- await client.Authenticator.Authenticate(client, request);
+ await client.Options.Authenticator!.Authenticate(client, request);
var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList();
@@ -73,15 +73,13 @@ public async Task Check_Only_Header_Authorization() {
[Fact]
public async Task Set_Auth_Header_Only_Once() {
- var client = new RestClient();
+ var client = new RestClient(new RestClientOptions { Authenticator = new JwtAuthenticator(_testJwt) });
var request = new RestRequest();
request.AddHeader(KnownHeaders.Authorization, "second_header_auth_token");
- client.Authenticator = new JwtAuthenticator(_testJwt);
-
//In real case client.Execute(...) will invoke Authenticate method
- await client.Authenticator.Authenticate(client, request);
+ await client.Options.Authenticator!.Authenticate(client, request);
var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList();
@@ -96,16 +94,15 @@ public async Task Set_Auth_Header_Only_Once() {
[Fact]
public async Task Updates_Auth_Header() {
- var client = new RestClient();
var request = new RestRequest();
var authenticator = new JwtAuthenticator(_expectedAuthHeaderContent);
- client.Authenticator = authenticator;
- await client.Authenticator.Authenticate(client, request);
+ var client = new RestClient(new RestClientOptions { Authenticator = authenticator });
+ await client.Options.Authenticator!.Authenticate(client, request);
authenticator.SetBearerToken("second_header_auth_token");
- await client.Authenticator.Authenticate(client, request);
+ await client.Options.Authenticator.Authenticate(client, request);
var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList();
@@ -124,4 +121,4 @@ public void Throw_Argument_Null_Exception() {
Assert.Equal("accessToken", exception.ParamName);
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests/OAuth1AuthenticatorTests.cs b/test/RestSharp.Tests/OAuth1AuthenticatorTests.cs
index 795257a78..f6832c666 100644
--- a/test/RestSharp.Tests/OAuth1AuthenticatorTests.cs
+++ b/test/RestSharp.Tests/OAuth1AuthenticatorTests.cs
@@ -70,81 +70,25 @@ public void Authenticate_ShouldAddSignatureToRequestAsSeparateParameters_OnUrlOr
// Assert
var parameters = request.Parameters;
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "x_auth_username" &&
- (string)x.Value == "ClientUsername" &&
- x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "x_auth_password" &&
- (string)x.Value == "ClientPassword" &&
- x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost && x.Name == "x_auth_mode" && (string)x.Value == "client_auth" && x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "oauth_consumer_key" &&
- (string)x.Value == "ConsumerKey" &&
- x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "oauth_signature" &&
- !string.IsNullOrWhiteSpace((string)x.Value) &&
- x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "oauth_signature_method" &&
- (string)x.Value == "PLAINTEXT" &&
- x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost && x.Name == "oauth_version" && (string)x.Value == "Version" && x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "oauth_nonce" &&
- !string.IsNullOrWhiteSpace((string)x.Value) &&
- x.ContentType == null
- )
- );
-
- Assert.NotNull(
- parameters.FirstOrDefault(
- x => x.Type == ParameterType.GetOrPost &&
- x.Name == "oauth_timestamp" &&
- !string.IsNullOrWhiteSpace((string)x.Value) &&
- x.ContentType == null
- )
- );
+ ParameterShouldBe("x_auth_username", "ClientUsername");
+ ParameterShouldBe("x_auth_password", "ClientPassword");
+ ParameterShouldBe("x_auth_mode", "client_auth");
+ ParameterShouldBe("oauth_consumer_key", "ConsumerKey");
+ ParameterShouldHaveValue("oauth_signature");
+ ParameterShouldBe("oauth_signature_method", "PLAINTEXT");
+ ParameterShouldBe("oauth_version", "Version");
+ ParameterShouldHaveValue("oauth_nonce");
+ ParameterShouldHaveValue("oauth_timestamp");
+
+ void ParameterShould(string name, Func check) {
+ var parameter = parameters.FirstOrDefault(x => x.Type == ParameterType.GetOrPost && x.Name == name);
+ parameter.Should().NotBeNull();
+ check(parameter).Should().BeTrue();
+ }
+
+ void ParameterShouldBe(string name, string value) => ParameterShould(name, x => (string)x.Value == value);
+
+ void ParameterShouldHaveValue(string name) => ParameterShould(name, x => !string.IsNullOrWhiteSpace((string)x.Value));
}
[Theory]
@@ -200,4 +144,4 @@ public void Authenticate_ShouldAllowEmptyConsumerSecret_OnHttpAuthorizationHeade
Assert.Contains("OAuth", value!);
Assert.Contains($"oauth_signature=\"{OAuthTools.UrlEncodeStrict("&")}", value);
}
-}
\ No newline at end of file
+}
diff --git a/test/RestSharp.Tests/ObjectParserTests.cs b/test/RestSharp.Tests/ObjectParserTests.cs
index 0df8b17a0..414c6d149 100644
--- a/test/RestSharp.Tests/ObjectParserTests.cs
+++ b/test/RestSharp.Tests/ObjectParserTests.cs
@@ -1,5 +1,3 @@
-using System.Globalization;
-
namespace RestSharp.Tests;
public class ObjectParserTests {
@@ -31,7 +29,7 @@ public void ShouldProduceMultipleParametersForArray() {
SomeIds = new[] { 1, 2, 3 }
};
var expected = request.SomeIds.Select(x => ("ids[]", x.ToString()));
- var parsed = request.GetProperties();
+ var parsed = request.GetProperties().Select(x => (x.Name, x.Value));
parsed.Should().BeEquivalentTo(expected);
}
diff --git a/test/RestSharp.Tests/ParametersTests.cs b/test/RestSharp.Tests/ParametersTests.cs
index 8bfc6edf3..719e96ed5 100644
--- a/test/RestSharp.Tests/ParametersTests.cs
+++ b/test/RestSharp.Tests/ParametersTests.cs
@@ -1,4 +1,5 @@
using System.Collections;
+using System.IO;
namespace RestSharp.Tests;
@@ -26,10 +27,49 @@ public void AddDefaultHeadersUsingDictionary() {
public void AddUrlSegmentWithInt() {
const string name = "foo";
+
var request = new RestRequest().AddUrlSegment(name, 1);
var actual = request.Parameters.FirstOrDefault(x => x.Name == name);
var expected = new UrlSegmentParameter(name, "1");
expected.Should().BeEquivalentTo(actual);
}
+
+ [Fact]
+ public void AddUrlSegmentModifiesUrlSegmentWithInt() {
+ const string name = "foo";
+ var pathTemplate = "/{0}/resource";
+ var path = String.Format(pathTemplate, "{" + name + "}");
+ var urlSegmentValue = 1;
+
+ var request = new RestRequest(path).AddUrlSegment(name, urlSegmentValue);
+
+ var expected = String.Format(pathTemplate, urlSegmentValue);
+
+ var client = new RestClient(BaseUrl);
+
+ var actual = client.BuildUri(request).AbsolutePath;
+
+
+ expected.Should().BeEquivalentTo(actual);
+ }
+
+ [Fact]
+ public void AddUrlSegmentModifiesUrlSegmentWithString() {
+ const string name = "foo";
+ var pathTemplate = "/{0}/resource";
+ var path = String.Format(pathTemplate, "{" + name + "}");
+ var urlSegmentValue = "bar";
+
+ var request = new RestRequest(path).AddUrlSegment(name, urlSegmentValue);
+
+ var expected = String.Format(pathTemplate, urlSegmentValue);
+
+ var client = new RestClient(BaseUrl);
+
+ var actual = client.BuildUri(request).AbsolutePath;
+
+ expected.Should().BeEquivalentTo(actual);
+
+ }
}
\ No newline at end of file
diff --git a/test/RestSharp.Tests/RestClientTests.cs b/test/RestSharp.Tests/RestClientTests.cs
index 7d7a675c2..7dd4fe618 100644
--- a/test/RestSharp.Tests/RestClientTests.cs
+++ b/test/RestSharp.Tests/RestClientTests.cs
@@ -1,3 +1,4 @@
+using RestSharp.Serializers;
using RestSharp.Serializers.Json;
namespace RestSharp.Tests;
@@ -24,11 +25,11 @@ public async Task Execute_with_IRestRequest_and_Method_overrides_previous_reques
}
[Fact]
- public void ConfigureHttp_will_set_proxy_to_null_with_no_exceptions_When_no_proxy_can_be_found() {
+ public async Task ConfigureHttp_will_set_proxy_to_null_with_no_exceptions_When_no_proxy_can_be_found() {
var req = new RestRequest();
var client = new RestClient(new RestClientOptions(BaseUrl) { Proxy = null });
- client.ExecuteAsync(req);
+ await client.ExecuteAsync(req);
}
[Fact]
@@ -64,15 +65,14 @@ public void BuildUri_should_build_with_passing_link_as_Uri_with_set_BaseUrl() {
[Fact]
public void UseJson_leaves_only_json_serializer() {
// arrange
- var baseUrl = new Uri(BaseUrl);
+ var baseUrl = new Uri(BaseUrl);
// act
- var client = new RestClient(baseUrl);
- client.UseJson();
+ var client = new RestClient(baseUrl, configureSerialization: cfg => cfg.UseJson());
// assert
- Assert.Single(client.Serializers);
- Assert.True(client.Serializers.ContainsKey(DataFormat.Json));
+ client.Serializers.Serializers.Should().HaveCount(1);
+ client.Serializers.GetSerializer(DataFormat.Json).Should().NotBeNull();
}
[Fact]
@@ -81,12 +81,11 @@ public void UseXml_leaves_only_json_serializer() {
var baseUrl = new Uri(BaseUrl);
// act
- var client = new RestClient(baseUrl);
- client.UseXml();
+ var client = new RestClient(baseUrl, configureSerialization: cfg => cfg.UseXml());
// assert
- Assert.Single(client.Serializers);
- Assert.True(client.Serializers.ContainsKey(DataFormat.Xml));
+ client.Serializers.Serializers.Should().HaveCount(1);
+ client.Serializers.GetSerializer(DataFormat.Xml).Should().NotBeNull();
}
[Fact]
@@ -95,11 +94,26 @@ public void UseOnlySerializer_leaves_only_custom_serializer() {
var baseUrl = new Uri(BaseUrl);
// act
- var client = new RestClient(baseUrl);
- client.UseOnlySerializer(() => new SystemTextJsonSerializer());
+ var client = new RestClient(baseUrl, configureSerialization: cfg => cfg.UseOnlySerializer(() => new SystemTextJsonSerializer()));
// assert
- Assert.Single(client.Serializers);
- Assert.True(client.Serializers.ContainsKey(DataFormat.Json));
+ client.Serializers.Serializers.Should().HaveCount(1);
+ client.Serializers.GetSerializer(DataFormat.Json).Should().NotBeNull();
+ }
+
+ [Fact]
+ public void Should_reuse_httpClient_instance() {
+ var client1 = new RestClient(new Uri("https://fake.api"), useClientFactory: true);
+ var client2 = new RestClient(new Uri("https://fake.api"), useClientFactory: true);
+
+ client1.HttpClient.Should().BeSameAs(client2.HttpClient);
+ }
+
+ [Fact]
+ public void Should_use_new_httpClient_instance() {
+ var client1 = new RestClient(new Uri("https://fake.api"));
+ var client2 = new RestClient(new Uri("https://fake.api"));
+
+ client1.HttpClient.Should().NotBeSameAs(client2.HttpClient);
}
-}
\ No newline at end of file
+}