diff --git a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/Serialization/Utf8JsonWriterHelper.cs b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/Serialization/Utf8JsonWriterHelper.cs
index de62d173e94..7292a3917fa 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/Serialization/Utf8JsonWriterHelper.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/Serialization/Utf8JsonWriterHelper.cs
@@ -206,14 +206,7 @@ private static void WriteList(
for (var i = 0; i < list.Count; i++)
{
- var element = list[i];
-
- if (element is null)
- {
- continue;
- }
-
- WriteFieldValue(writer, element);
+ WriteFieldValue(writer, list[i]);
}
writer.WriteEndArray();
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpResponse.cs b/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpResponse.cs
index 2d63c01c651..dc0747a6ff1 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpResponse.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpResponse.cs
@@ -53,6 +53,11 @@ public GraphQLHttpResponse(HttpResponseMessage message)
///
public bool IsSuccessStatusCode => _message.IsSuccessStatusCode;
+ ///
+ /// Gets the reason phrase which typically is sent by servers together with the status code.
+ ///
+ public string? ReasonPhrase => _message.ReasonPhrase;
+
///
/// Throws an exception if the HTTP response was unsuccessful.
///
diff --git a/src/HotChocolate/Utilities/src/Utilities/HotChocolate.Utilities.csproj b/src/HotChocolate/Utilities/src/Utilities/HotChocolate.Utilities.csproj
index 379a690277f..056320160bc 100644
--- a/src/HotChocolate/Utilities/src/Utilities/HotChocolate.Utilities.csproj
+++ b/src/HotChocolate/Utilities/src/Utilities/HotChocolate.Utilities.csproj
@@ -20,6 +20,7 @@
+
diff --git a/src/StrawberryShake/Client/StrawberryShake.Client.sln b/src/StrawberryShake/Client/StrawberryShake.Client.sln
index 461f94d5ba9..403d8513044 100644
--- a/src/StrawberryShake/Client/StrawberryShake.Client.sln
+++ b/src/StrawberryShake/Client/StrawberryShake.Client.sln
@@ -37,6 +37,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrawberryShake.Persistence
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrawberryShake.Razor", "src\Razor\StrawberryShake.Razor.csproj", "{2A834588-BA60-4906-B111-20AF9FD5B1E6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Transport.Http", "..\..\HotChocolate\AspNetCore\src\Transport.Http\HotChocolate.Transport.Http.csproj", "{1A765D49-CF64-4C34-AE37-E6171362A858}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Utilities", "..\..\HotChocolate\Utilities\src\Utilities\HotChocolate.Utilities.csproj", "{62478E17-8EAE-4473-9C8E-5933042B25A0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -218,6 +222,30 @@ Global
{2A834588-BA60-4906-B111-20AF9FD5B1E6}.Release|x64.Build.0 = Release|Any CPU
{2A834588-BA60-4906-B111-20AF9FD5B1E6}.Release|x86.ActiveCfg = Release|Any CPU
{2A834588-BA60-4906-B111-20AF9FD5B1E6}.Release|x86.Build.0 = Release|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Debug|x64.Build.0 = Debug|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Debug|x86.Build.0 = Debug|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Release|x64.ActiveCfg = Release|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Release|x64.Build.0 = Release|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Release|x86.ActiveCfg = Release|Any CPU
+ {1A765D49-CF64-4C34-AE37-E6171362A858}.Release|x86.Build.0 = Release|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Debug|x64.Build.0 = Debug|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Debug|x86.Build.0 = Debug|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Release|x64.ActiveCfg = Release|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Release|x64.Build.0 = Release|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Release|x86.ActiveCfg = Release|Any CPU
+ {62478E17-8EAE-4473-9C8E-5933042B25A0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{61894B9B-EC9A-4FC0-AAAF-9922978A5AB8} = {5F283FE4-BB0A-4806-B079-42DBFE0B2EDF}
@@ -234,5 +262,7 @@ Global
{DFFA574E-439F-4FCE-BF6D-CE5BEACA360A} = {5F283FE4-BB0A-4806-B079-42DBFE0B2EDF}
{9A5345E8-A2EB-410F-BD72-6F268794D2DC} = {4E0B9145-BE75-4F46-906B-092DFF4AD5CC}
{2A834588-BA60-4906-B111-20AF9FD5B1E6} = {5F283FE4-BB0A-4806-B079-42DBFE0B2EDF}
+ {1A765D49-CF64-4C34-AE37-E6171362A858} = {264FDF20-A58F-476E-9E02-9F082FB65612}
+ {62478E17-8EAE-4473-9C8E-5933042B25A0} = {264FDF20-A58F-476E-9E02-9F082FB65612}
EndGlobalSection
EndGlobal
diff --git a/src/StrawberryShake/Client/src/Resources/Properties/Resources.Designer.cs b/src/StrawberryShake/Client/src/Resources/Properties/Resources.Designer.cs
index abc3c75adb8..b63691b1bf4 100644
--- a/src/StrawberryShake/Client/src/Resources/Properties/Resources.Designer.cs
+++ b/src/StrawberryShake/Client/src/Resources/Properties/Resources.Designer.cs
@@ -278,5 +278,11 @@ internal static string ResponseEnumerator_HttpNoSuccessStatusCode {
return ResourceManager.GetString("ResponseEnumerator_HttpNoSuccessStatusCode", resourceCulture);
}
}
+
+ internal static string HttpConnection_FileMapDoesNotMatch {
+ get {
+ return ResourceManager.GetString("HttpConnection_FileMapDoesNotMatch", resourceCulture);
+ }
+ }
}
}
diff --git a/src/StrawberryShake/Client/src/Resources/Properties/Resources.resx b/src/StrawberryShake/Client/src/Resources/Properties/Resources.resx
index 110dd126178..4615c8ef898 100644
--- a/src/StrawberryShake/Client/src/Resources/Properties/Resources.resx
+++ b/src/StrawberryShake/Client/src/Resources/Properties/Resources.resx
@@ -135,4 +135,7 @@
Response status code does not indicate success: {0} ({1}).
+
+ Could not map the file uploads to the variables. The data structures did not match. Could not find path {0} in the variables.
+
diff --git a/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs b/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs
index bfeebe2da83..4291a844737 100644
--- a/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs
+++ b/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs
@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
+using System.Text;
using System.Text.Json;
-using StrawberryShake.Internal;
+using HotChocolate.Transport.Http;
+using HotChocolate.Utilities;
using StrawberryShake.Json;
+using static StrawberryShake.Properties.Resources;
using static StrawberryShake.Transport.Http.ResponseEnumerable;
namespace StrawberryShake.Transport.Http;
@@ -13,7 +14,6 @@ namespace StrawberryShake.Transport.Http;
public sealed class HttpConnection : IHttpConnection
{
private readonly Func _createClient;
- private readonly JsonOperationRequestSerializer _serializer = new();
public HttpConnection(Func createClient)
{
@@ -21,54 +21,200 @@ public HttpConnection(Func createClient)
}
public IAsyncEnumerable> ExecuteAsync(OperationRequest request)
- => Create(_createClient, () => CreateRequestMessage(request));
+ => Create(_createClient, () => MapRequest(request));
- private HttpRequestMessage CreateRequestMessage(OperationRequest request)
+ private static GraphQLHttpRequest MapRequest(OperationRequest request)
{
- var operation = CreateRequestMessageBody(request);
+ var (id, name, document, variables, extensions, _, files, _) = request;
- var content = request.Files.Count == 0
- ? CreateRequestContent(operation)
- : CreateMultipartContent(request, operation);
+#if NETSTANDARD2_0
+ var body = Encoding.UTF8.GetString(document.Body.ToArray());
+#else
+ var body = Encoding.UTF8.GetString(document.Body);
+#endif
- return new HttpRequestMessage { Method = HttpMethod.Post, Content = content };
+ var hasFiles = files is { Count: > 0 };
+
+ variables = MapVariables(variables);
+ if (hasFiles && variables is not null)
+ {
+ variables = MapFilesToVariables(variables, files!);
+ }
+
+ var operation =
+ new HotChocolate.Transport.OperationRequest(body, id, name, variables, extensions);
+
+ return new GraphQLHttpRequest(operation) { EnableFileUploads = hasFiles };
}
- private byte[] CreateRequestMessageBody(OperationRequest request)
+ ///
+ /// Converts the variables into a dictionary that can be serialized. This is necessary
+ /// because the variables can contain lists of key value pairs which are not supported
+ /// by HotChocolate.Transport.Http
+ ///
+ ///
+ /// We only convert the variables if necessary to avoid unnecessary allocations.
+ ///
+ private static IReadOnlyDictionary? MapVariables(
+ IReadOnlyDictionary variables)
{
- using var arrayWriter = new ArrayWriter();
- _serializer.Serialize(request, arrayWriter);
- var buffer = new byte[arrayWriter.Length];
- arrayWriter.Body.Span.CopyTo(buffer);
- return buffer;
+ if (variables.Count == 0)
+ {
+ return null;
+ }
+
+ Dictionary? copy = null;
+ foreach (var variable in variables)
+ {
+ var value = variable.Value;
+ // the value can be a List of key value pairs and not only a dictionary. We do expect
+ // to just have lists here, but in case we have a dictionary this should also just work.
+ if (value is IEnumerable> items)
+ {
+ copy ??= CreateDictionary(variables);
+
+ value = MapVariables(CreateDictionary(items));
+ }
+ else if (value is List