diff --git a/src/All.slnx b/src/All.slnx
index b72a38e5519..64f38dd7e4e 100644
--- a/src/All.slnx
+++ b/src/All.slnx
@@ -40,12 +40,13 @@
+
+
-
@@ -67,12 +68,12 @@
-
+
-
+
@@ -95,8 +96,8 @@
-
+
@@ -174,6 +175,7 @@
+
@@ -182,6 +184,7 @@
+
@@ -230,12 +233,12 @@
+
-
-
+
@@ -275,9 +278,9 @@
-
+
@@ -326,4 +329,4 @@
-
+
\ No newline at end of file
diff --git a/src/CookieCrumble/src/CookieCrumble.HotChocolate/Formatters/OperationResultSnapshotValueFormatter.cs b/src/CookieCrumble/src/CookieCrumble.HotChocolate/Formatters/OperationResultSnapshotValueFormatter.cs
index e326d82e2a6..b3db816d674 100644
--- a/src/CookieCrumble/src/CookieCrumble.HotChocolate/Formatters/OperationResultSnapshotValueFormatter.cs
+++ b/src/CookieCrumble/src/CookieCrumble.HotChocolate/Formatters/OperationResultSnapshotValueFormatter.cs
@@ -9,44 +9,46 @@ internal sealed class OperationResultSnapshotValueFormatter : SnapshotValueForma
{
protected override void Format(IBufferWriter snapshot, OperationResult value)
{
- var next = false;
+ var writer = new Utf8JsonWriter(
+ snapshot,
+ new JsonWriterOptions
+ {
+ Indented = true,
+ SkipValidation = true,
+ Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ });
+
+ writer.WriteStartObject();
if (value.RequestIndex.HasValue)
{
- snapshot.Append("RequestIndex: ");
- snapshot.Append(value.RequestIndex.Value.ToString());
- next = true;
+ writer.WriteNumber("requestIndex", value.RequestIndex.Value);
}
if (value.VariableIndex.HasValue)
{
- snapshot.AppendLine(appendWhenTrue: next);
- snapshot.Append("VariableIndex: ");
- snapshot.Append(value.VariableIndex.Value.ToString());
- next = true;
+ writer.WriteNumber("variableIndex", value.VariableIndex.Value);
}
if (value.Data.ValueKind is JsonValueKind.Object)
{
- snapshot.AppendLine(appendWhenTrue: next);
- snapshot.Append("Data: ");
- snapshot.Append(value.Data.ToString());
- next = true;
+ writer.WritePropertyName("data");
+ value.Data.WriteTo(writer);
}
if (value.Errors.ValueKind is JsonValueKind.Array)
{
- snapshot.AppendLine(appendWhenTrue: next);
- snapshot.Append("Errors: ");
- snapshot.Append(value.Errors.ToString());
- next = true;
+ writer.WritePropertyName("errors");
+ value.Errors.WriteTo(writer);
}
if (value.Extensions.ValueKind is JsonValueKind.Object)
{
- snapshot.AppendLine(appendWhenTrue: next);
- snapshot.Append("Extensions: ");
- snapshot.Append(value.Extensions.ToString());
+ writer.WritePropertyName("extensions");
+ value.Extensions.WriteTo(writer);
}
+
+ writer.WriteEndObject();
+ writer.Flush();
}
}
diff --git a/src/HotChocolate/AspNetCore/HotChocolate.AspNetCore.slnx b/src/HotChocolate/AspNetCore/HotChocolate.AspNetCore.slnx
index c2295ee8086..2c97a1be58c 100644
--- a/src/HotChocolate/AspNetCore/HotChocolate.AspNetCore.slnx
+++ b/src/HotChocolate/AspNetCore/HotChocolate.AspNetCore.slnx
@@ -3,6 +3,7 @@
+
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/AcceptMediaType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AcceptMediaType.cs
similarity index 76%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/AcceptMediaType.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AcceptMediaType.cs
index f3c42e68015..81fcec0d576 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/AcceptMediaType.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AcceptMediaType.cs
@@ -1,4 +1,3 @@
-using HotChocolate.Utilities;
using Microsoft.Extensions.Primitives;
namespace HotChocolate.AspNetCore;
@@ -48,45 +47,47 @@ internal AcceptMediaType(
Charset = charset.HasValue ? charset.Value : null;
IsUtf8 = Charset?.Equals("utf-8", StringComparison.OrdinalIgnoreCase) ?? true;
- if (Type.EqualsOrdinal(ContentType.Types.All) && SubType.EqualsOrdinal(ContentType.Types.All))
+ if (Type.Equals(ContentType.Types.All, StringComparison.Ordinal)
+ && SubType.Equals(ContentType.Types.All, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.All;
}
- else if (Type.EqualsOrdinal(ContentType.Types.Application))
+ else if (Type.Equals(ContentType.Types.Application, StringComparison.Ordinal))
{
- if (SubType.EqualsOrdinal(ContentType.Types.All))
+ if (SubType.Equals(ContentType.Types.All, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.AllApplication;
}
- else if (SubType.EqualsOrdinal(ContentType.SubTypes.GraphQLResponse))
+ else if (SubType.Equals(ContentType.SubTypes.GraphQLResponse, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.ApplicationGraphQL;
}
- else if (SubType.EqualsOrdinal(ContentType.SubTypes.GraphQLResponseStream))
+ else if (SubType.Equals(ContentType.SubTypes.GraphQLResponseStream, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.ApplicationGraphQLStream;
}
- else if (SubType.EqualsOrdinal(ContentType.SubTypes.Json))
+ else if (SubType.Equals(ContentType.SubTypes.Json, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.ApplicationJson;
}
- else if (SubType.EqualsOrdinal(ContentType.SubTypes.JsonLines))
+ else if (SubType.Equals(ContentType.SubTypes.JsonLines, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.ApplicationJsonLines;
}
}
- else if (Type.EqualsOrdinal(ContentType.Types.MultiPart))
+ else if (Type.Equals(ContentType.Types.MultiPart, StringComparison.Ordinal))
{
- if (SubType.EqualsOrdinal(ContentType.Types.All))
+ if (SubType.Equals(ContentType.Types.All, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.AllMultiPart;
}
- else if (SubType.EqualsOrdinal(ContentType.SubTypes.Mixed))
+ else if (SubType.Equals(ContentType.SubTypes.Mixed, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.MultiPartMixed;
}
}
- else if (Type.EqualsOrdinal(ContentType.Types.Text) && SubType.EqualsOrdinal(ContentType.SubTypes.EventStream))
+ else if (Type.Equals(ContentType.Types.Text, StringComparison.Ordinal)
+ && SubType.Equals(ContentType.SubTypes.EventStream, StringComparison.Ordinal))
{
Kind = AcceptMediaTypeKind.EventStream;
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/AcceptMediaTypeKind.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AcceptMediaTypeKind.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/AcceptMediaTypeKind.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AcceptMediaTypeKind.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/AllowedGetOperations.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AllowedGetOperations.cs
similarity index 93%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/AllowedGetOperations.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AllowedGetOperations.cs
index 193f94fdc26..fda0d645e76 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/AllowedGetOperations.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/AllowedGetOperations.cs
@@ -3,6 +3,7 @@ namespace HotChocolate.AspNetCore;
[Flags]
public enum AllowedGetOperations
{
+ None = 0,
Query = 1,
Mutation = 2,
Subscription = 4,
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ContentType.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ContentType.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpMethod.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/DefaultHttpMethod.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpMethod.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/DefaultHttpMethod.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs
new file mode 100644
index 00000000000..47298358679
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ExecutorSession.cs
@@ -0,0 +1,165 @@
+using System.Net;
+using HotChocolate.AspNetCore.Formatters;
+using HotChocolate.AspNetCore.Instrumentation;
+using HotChocolate.AspNetCore.Parsers;
+using HotChocolate.Features;
+using HotChocolate.Language;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate.AspNetCore;
+
+public sealed class ExecutorSession
+ : IRequestExecutor
+ , IHttpRequestInterceptor
+ , IErrorHandler
+{
+ private readonly IRequestExecutor _executor;
+ private readonly IErrorHandler _errorHandler;
+ private readonly IHttpRequestInterceptor _requestInterceptor;
+ private readonly ISocketSessionInterceptor _socketSessionInterceptor;
+ private readonly IHttpRequestParser _requestParser;
+ private readonly IHttpResponseFormatter _responseFormatter;
+ private readonly IServerDiagnosticEvents _diagnosticEvents;
+
+ public ExecutorSession(IRequestExecutor executor)
+ {
+ ArgumentNullException.ThrowIfNull(executor);
+
+ _executor = executor;
+ _errorHandler = executor.Schema.Services.GetRequiredService();
+ _requestInterceptor = executor.Schema.Services.GetRequiredService();
+ _socketSessionInterceptor = executor.Schema.Services.GetRequiredService();
+ _responseFormatter = executor.Schema.Services.GetRequiredService();
+ _requestParser = executor.Schema.Services.GetRequiredService();
+ _diagnosticEvents = executor.Schema.Services.GetRequiredService();
+ }
+
+ public ISocketSessionInterceptor SocketSessionInterceptor => _socketSessionInterceptor;
+
+ public IHttpResponseFormatter ResponseFormatter => _responseFormatter;
+
+ public IHttpRequestParser RequestParser => _requestParser;
+
+ public IServerDiagnosticEvents DiagnosticEvents => _diagnosticEvents;
+
+ public ulong Version => _executor.Version;
+
+ public ISchemaDefinition Schema => _executor.Schema;
+
+ public IFeatureCollection Features => _executor.Features;
+
+ public Task ExecuteAsync(
+ IOperationRequest request,
+ CancellationToken cancellationToken = default)
+ => _executor.ExecuteAsync(request, cancellationToken);
+
+ public Task ExecuteBatchAsync(
+ OperationRequestBatch requestBatch,
+ CancellationToken cancellationToken = default)
+ => _executor.ExecuteBatchAsync(requestBatch, cancellationToken);
+
+ public IError Handle(IError error)
+ => _errorHandler.Handle(error);
+
+ public ValueTask OnCreateAsync(
+ HttpContext context,
+ OperationRequestBuilder requestBuilder,
+ CancellationToken cancellationToken)
+ => _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, cancellationToken);
+
+ public ValueTask OnCreateAsync(
+ HttpContext context,
+ IRequestExecutor requestExecutor,
+ OperationRequestBuilder requestBuilder,
+ CancellationToken cancellationToken)
+ => _requestInterceptor.OnCreateAsync(context, requestExecutor, requestBuilder, cancellationToken);
+
+ public async Task ExecuteSingleAsync(
+ HttpContext context,
+ GraphQLRequest request,
+ RequestFlags flags)
+ {
+ _diagnosticEvents.StartSingleRequest(context, request);
+
+ var requestBuilder = OperationRequestBuilder.From(request);
+ requestBuilder.SetFlags(flags);
+
+ await _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, context.RequestAborted);
+
+ return await _executor.ExecuteAsync(requestBuilder.Build(), context.RequestAborted);
+ }
+
+ public async Task ExecuteOperationBatchAsync(
+ HttpContext context,
+ GraphQLRequest request,
+ RequestFlags flags,
+ IReadOnlyList operationNames)
+ {
+ _diagnosticEvents.StartOperationBatchRequest(context, request, operationNames);
+
+ var requestBatch = new IOperationRequest[operationNames.Count];
+
+ for (var i = 0; i < operationNames.Count; i++)
+ {
+ var requestBuilder = OperationRequestBuilder.From(request);
+ requestBuilder.SetOperationName(operationNames[i]);
+ requestBuilder.SetFlags(flags);
+
+ await _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, context.RequestAborted);
+
+ requestBatch[i] = requestBuilder.Build();
+ }
+
+ return await _executor.ExecuteBatchAsync(
+ new OperationRequestBatch(requestBatch, services: context.RequestServices),
+ cancellationToken: context.RequestAborted);
+ }
+
+ public async Task ExecuteBatchAsync(
+ HttpContext context,
+ IReadOnlyList requests,
+ RequestFlags flags)
+ {
+ _diagnosticEvents.StartBatchRequest(context, requests);
+
+ var requestBatch = new IOperationRequest[requests.Count];
+
+ for (var i = 0; i < requests.Count; i++)
+ {
+ var requestBuilder = OperationRequestBuilder.From(requests[i]);
+ requestBuilder.SetFlags(flags);
+
+ await _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, context.RequestAborted);
+
+ requestBatch[i] = requestBuilder.Build();
+ }
+
+ return await _executor.ExecuteBatchAsync(
+ new OperationRequestBatch(requestBatch, services: context.RequestServices),
+ cancellationToken: context.RequestAborted);
+ }
+
+ public ValueTask WriteResultAsync(
+ HttpContext context,
+ IExecutionResult result,
+ AcceptMediaType[] acceptMediaTypes,
+ HttpStatusCode? statusCode = null)
+ => _responseFormatter.FormatAsync(
+ context.Response,
+ result,
+ acceptMediaTypes,
+ statusCode,
+ context.RequestAborted);
+
+ public async Task WriteSchemaAsync(
+ HttpContext context)
+ => await _responseFormatter.FormatAsync(
+ context.Response,
+ Schema,
+ Version,
+ context.RequestAborted);
+
+ public RequestFlags CreateRequestFlags(AcceptMediaType[] acceptMediaTypes)
+ => _responseFormatter.CreateRequestFlags(acceptMediaTypes);
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs
similarity index 82%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs
index ad07c4d7f77..fb91b486056 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/EndpointRouteBuilderExtensions.cs
@@ -1,12 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using ChilliCream.Nitro.App;
+using HotChocolate.AspNetCore;
+using HotChocolate.AspNetCore.Extensions;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
-using HotChocolate.AspNetCore;
-using HotChocolate.AspNetCore.Extensions;
-using static HotChocolate.AspNetCore.MiddlewareRoutingType;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory;
+using MiddlewareFactory = HotChocolate.AspNetCore.MiddlewareFactory;
// ReSharper disable once CheckNamespace
namespace Microsoft.AspNetCore.Builder;
@@ -108,23 +111,28 @@ public static IApplicationBuilder MapGraphQL(
string schemaName)
{
ArgumentNullException.ThrowIfNull(applicationBuilder);
+ ArgumentException.ThrowIfNullOrEmpty(schemaName);
path = path.ToString().TrimEnd('/');
+ var executorProvider = applicationBuilder.ApplicationServices.GetRequiredService();
+ var executorEvents = applicationBuilder.ApplicationServices.GetRequiredService();
+ var formOptions = applicationBuilder.ApplicationServices.GetRequiredService>();
+ var executor = new HttpRequestExecutorProxy(executorProvider, executorEvents, schemaName);
+
applicationBuilder
- .UseCancellation()
- .UseMiddleware(schemaName)
- .UseMiddleware(schemaName)
- .UseMiddleware(schemaName)
- .UseMiddleware(schemaName)
- .UseMiddleware(schemaName, path, Integrated)
+ .Use(MiddlewareFactory.CreateCancellationMiddleware())
+ .Use(MiddlewareFactory.CreateWebSocketSubscriptionMiddleware(executor))
+ .Use(MiddlewareFactory.CreateHttpPostMiddleware(executor))
+ .Use(MiddlewareFactory.CreateHttpMultipartMiddleware(executor, formOptions))
+ .Use(MiddlewareFactory.CreateHttpGetMiddleware(executor))
+ .Use(MiddlewareFactory.CreateHttpGetSchemaMiddleware(executor, path, MiddlewareRoutingType.Integrated))
.UseNitroApp(path)
- .Use(
- _ => context =>
- {
- context.Response.StatusCode = 404;
- return Task.CompletedTask;
- });
+ .Use(_ => context =>
+ {
+ context.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ });
return applicationBuilder;
}
@@ -184,17 +192,21 @@ public static GraphQLHttpEndpointConventionBuilder MapGraphQLHttp(
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
+ var executorProvider = endpointRouteBuilder.ServiceProvider.GetRequiredService();
+ var executorEvents = endpointRouteBuilder.ServiceProvider.GetRequiredService();
+ var formOptions = endpointRouteBuilder.ServiceProvider.GetRequiredService>();
+ var executor = new HttpRequestExecutorProxy(executorProvider, executorEvents, schemaNameOrDefault);
+
requestPipeline
- .UseCancellation()
- .UseMiddleware(schemaNameOrDefault)
- .UseMiddleware(schemaNameOrDefault)
- .UseMiddleware(schemaNameOrDefault)
- .Use(
- _ => context =>
- {
- context.Response.StatusCode = 404;
- return Task.CompletedTask;
- });
+ .Use(MiddlewareFactory.CreateCancellationMiddleware())
+ .Use(MiddlewareFactory.CreateHttpPostMiddleware(executor))
+ .Use(MiddlewareFactory.CreateHttpMultipartMiddleware(executor, formOptions))
+ .Use(MiddlewareFactory.CreateHttpGetMiddleware(executor))
+ .Use(_ => context =>
+ {
+ context.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ });
return new GraphQLHttpEndpointConventionBuilder(
endpointRouteBuilder
@@ -257,15 +269,18 @@ public static WebSocketEndpointConventionBuilder MapGraphQLWebSocket(
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
+ var executorProvider = endpointRouteBuilder.ServiceProvider.GetRequiredService();
+ var executorEvents = endpointRouteBuilder.ServiceProvider.GetRequiredService();
+ var executor = new HttpRequestExecutorProxy(executorProvider, executorEvents, schemaNameOrDefault);
+
requestPipeline
- .UseCancellation()
- .UseMiddleware(schemaNameOrDefault)
- .Use(
- _ => context =>
- {
- context.Response.StatusCode = 404;
- return Task.CompletedTask;
- });
+ .Use(MiddlewareFactory.CreateCancellationMiddleware())
+ .Use(MiddlewareFactory.CreateWebSocketSubscriptionMiddleware(executor))
+ .Use(_ => context =>
+ {
+ context.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ });
var builder = new GraphQLEndpointConventionBuilder(
endpointRouteBuilder
@@ -330,15 +345,18 @@ public static IEndpointConventionBuilder MapGraphQLSchema(
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
+ var executorProvider = endpointRouteBuilder.ServiceProvider.GetRequiredService();
+ var executorEvents = endpointRouteBuilder.ServiceProvider.GetRequiredService();
+ var executor = new HttpRequestExecutorProxy(executorProvider, executorEvents, schemaNameOrDefault);
+
requestPipeline
- .UseCancellation()
- .UseMiddleware(schemaNameOrDefault, PathString.Empty, Explicit)
- .Use(
- _ => context =>
- {
- context.Response.StatusCode = 404;
- return Task.CompletedTask;
- });
+ .Use(MiddlewareFactory.CreateCancellationMiddleware())
+ .Use(MiddlewareFactory.CreateHttpGetSchemaMiddleware(executor, PathString.Empty, MiddlewareRoutingType.Explicit))
+ .Use(_ => context =>
+ {
+ context.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ });
return new GraphQLEndpointConventionBuilder(
endpointRouteBuilder
@@ -399,12 +417,11 @@ public static NitroAppEndpointConventionBuilder MapNitroApp(
requestPipeline
.UseNitroApp(toolPath)
- .Use(
- _ => context =>
- {
- context.Response.StatusCode = 404;
- return Task.CompletedTask;
- });
+ .Use(_ => context =>
+ {
+ context.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ });
var builder = endpointRouteBuilder
.Map(pattern, requestPipeline.Build())
@@ -427,7 +444,7 @@ public static NitroAppEndpointConventionBuilder MapNitroApp(
/// The name of the schema that shall be used by this endpoint.
///
///
- /// Specifies if its required to provide the operation name as part of the URI.
+ /// Specifies if its required providing the operation name as part of the URI.
///
///
/// Returns the so that
@@ -452,7 +469,7 @@ public static IEndpointConventionBuilder MapGraphQLPersistedOperations(
/// The name of the schema that shall be used by this endpoint.
///
///
- /// Specifies if its required to provide the operation name as part of the URI.
+ /// Specifies if its required providing the operation name as part of the URI.
///
///
/// Returns the so that
@@ -463,10 +480,12 @@ public static IEndpointConventionBuilder MapGraphQLPersistedOperations(
string? schemaName = null,
bool requireOperationName = false)
{
- var schemaNameOrDefault = schemaName ?? ISchemaDefinition.DefaultName;
+ ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
+ ArgumentNullException.ThrowIfNull(path);
+ schemaName ??= ISchemaDefinition.DefaultName;
var group = endpointRouteBuilder.MapGroup(path);
- group.MapPersistedOperationMiddleware(schemaNameOrDefault, requireOperationName);
+ group.MapPersistedOperationMiddleware(endpointRouteBuilder.ServiceProvider, schemaName, requireOperationName);
return group;
}
@@ -553,20 +572,6 @@ public static WebSocketEndpointConventionBuilder WithOptions(
GraphQLSocketOptions socketOptions) =>
builder.WithMetadata(new GraphQLServerOptions { Sockets = socketOptions });
- private static IApplicationBuilder UseCancellation(this IApplicationBuilder builder)
- => builder.Use(
- next => async context =>
- {
- try
- {
- await next(context);
- }
- catch (OperationCanceledException)
- {
- // we just catch cancellations here and do nothing.
- }
- });
-
internal static NitroAppOptions ToNitroAppOptions(this GraphQLToolOptions options)
=> new()
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLEndpointConventionBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/GraphQLEndpointConventionBuilder.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLEndpointConventionBuilder.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/GraphQLEndpointConventionBuilder.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLHttpEndpointConventionBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/GraphQLHttpEndpointConventionBuilder.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLHttpEndpointConventionBuilder.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/GraphQLHttpEndpointConventionBuilder.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpContextExtensions.cs
similarity index 67%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpContextExtensions.cs
index 1f6170bb223..3b7e4157c15 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpContextExtensions.cs
@@ -27,6 +27,7 @@ public static bool IncludeQueryPlan(this HttpContext context)
return false;
}
+ // TODO : Implement this
public static string? TryGetCostSwitch(this HttpContext context)
{
var headers = context.Request.Headers;
@@ -42,15 +43,38 @@ public static bool IncludeQueryPlan(this HttpContext context)
if (value.Equals(HttpHeaderValues.ReportCost, StringComparison.OrdinalIgnoreCase))
{
- return WellKnownContextData.ReportCost;
+ return ExecutionContextData.ReportCost;
}
if (value.Equals(HttpHeaderValues.ValidateCost, StringComparison.OrdinalIgnoreCase))
{
- return WellKnownContextData.ValidateCost;
+ return ExecutionContextData.ValidateCost;
}
}
return null;
}
+
+ public static RequestContentType ParseContentType(this HttpContext context)
+ {
+ if (context.Items.TryGetValue(nameof(RequestContentType), out var value) &&
+ value is RequestContentType contentType)
+ {
+ return contentType;
+ }
+
+ var span = context.Request.ContentType.AsSpan();
+
+ if (span.StartsWith(ContentType.JsonSpan()))
+ {
+ return RequestContentType.Json;
+ }
+
+ if (span.StartsWith(ContentType.MultiPartFormSpan()))
+ {
+ return RequestContentType.Form;
+ }
+
+ return RequestContentType.None;
+ }
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpRequestExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpRequestExtensions.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpRequestExtensions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpRequestExtensions.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpResponseExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpResponseExtensions.cs
new file mode 100644
index 00000000000..bd9c014fcc2
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/HttpResponseExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Http;
+using static System.Globalization.CultureInfo;
+using static System.String;
+
+namespace HotChocolate.AspNetCore;
+
+internal static class HttpResponseExtensions
+{
+ private const string ContentDispositionHeader = "Content-Disposition";
+ private const string ContentDispositionValue = "attachment; filename=\"{0}\"";
+
+ public static IHeaderDictionary SetContentDisposition(
+ this IHeaderDictionary headers,
+ string fileName)
+ {
+ headers[ContentDispositionHeader] =
+ Format(InvariantCulture, ContentDispositionValue, fileName);
+ return headers;
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/NitroAppEndpointConventionBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/NitroAppEndpointConventionBuilder.cs
similarity index 83%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/NitroAppEndpointConventionBuilder.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/NitroAppEndpointConventionBuilder.cs
index 827858bffd5..a846c4cdc06 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/NitroAppEndpointConventionBuilder.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/NitroAppEndpointConventionBuilder.cs
@@ -15,6 +15,6 @@ internal NitroAppEndpointConventionBuilder(IEndpointConventionBuilder builder)
}
///
- public void Add(Action convention) =>
- _builder.Add(convention);
+ public void Add(Action convention)
+ => _builder.Add(convention);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/WebSocketEndpointConventionBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/WebSocketEndpointConventionBuilder.cs
similarity index 83%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/WebSocketEndpointConventionBuilder.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/WebSocketEndpointConventionBuilder.cs
index 46bc70368db..aa487856be5 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/WebSocketEndpointConventionBuilder.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Extensions/WebSocketEndpointConventionBuilder.cs
@@ -15,6 +15,6 @@ internal WebSocketEndpointConventionBuilder(IEndpointConventionBuilder builder)
}
///
- public void Add(Action convention) =>
- _builder.Add(convention);
+ public void Add(Action convention)
+ => _builder.Add(convention);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs
similarity index 95%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs
index 17289f41ed9..8b953c7a8d5 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultHttpResponseFormatter.cs
@@ -5,16 +5,15 @@
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
+using HotChocolate.AspNetCore.Utilities;
using HotChocolate.Transport.Formatters;
-using HotChocolate.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using static HotChocolate.AspNetCore.AcceptMediaTypeKind;
using static HotChocolate.Execution.ExecutionResultKind;
-using static HotChocolate.WellKnownContextData;
using HttpStatusCode = System.Net.HttpStatusCode;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Formatters;
///
/// This represents the default implementation for the
@@ -210,14 +209,14 @@ private async ValueTask FormatInternalAsync(
response.StatusCode = statusCode;
if (result.ContextData is not null
- && result.ContextData.TryGetValue(WellKnownContextData.CacheControlHeaderValue, out var value)
+ && result.ContextData.TryGetValue(ExecutionContextData.CacheControlHeaderValue, out var value)
&& value is CacheControlHeaderValue cacheControlHeaderValue)
{
response.GetTypedHeaders().CacheControl = cacheControlHeaderValue;
}
if (result.ContextData is not null
- && result.ContextData.TryGetValue(VaryHeaderValue, out var varyValue)
+ && result.ContextData.TryGetValue(ExecutionContextData.VaryHeaderValue, out var varyValue)
&& varyValue is string varyHeaderValue)
{
response.Headers.Vary = varyHeaderValue;
@@ -352,7 +351,7 @@ protected virtual HttpStatusCode OnDetermineStatusCode(
var contextData = result.ContextData;
// First, we check if there is an explicit HTTP status code override by the user.
- if (contextData.TryGetValue(WellKnownContextData.HttpStatusCode, out var value))
+ if (contextData.TryGetValue(ExecutionContextData.HttpStatusCode, out var value))
{
if (value is HttpStatusCode statusCode)
{
@@ -372,7 +371,7 @@ protected virtual HttpStatusCode OnDetermineStatusCode(
return HttpStatusCode.BadRequest;
}
- if (result.ContextData.ContainsKey(OperationNotAllowed))
+ if (result.ContextData.ContainsKey(ExecutionContextData.OperationNotAllowed))
{
return HttpStatusCode.MethodNotAllowed;
}
@@ -442,7 +441,7 @@ protected virtual HttpStatusCode OnDetermineStatusCode(
HttpStatusCode? proposedStatusCode)
{
// if we are sending a response stream with the multipart/mixed header or
- // with a text/event-stream response content-type we as well will just
+ // with a text/event-stream response content-type, we as well will just
// respond with an OK status code.
if (format.Kind is ResponseContentType.MultiPartMixed or ResponseContentType.EventStream)
{
@@ -450,7 +449,7 @@ protected virtual HttpStatusCode OnDetermineStatusCode(
}
// we allow for users to implement alternative protocols or response content-type.
- // if we end up here the user did not fully implement all necessary parts to add support
+ // if we end up here, the user did not fully implement all necessary parts to add support
// for an alternative protocols or response content-type.
throw ThrowHelper.Formatter_ResponseContentTypeNotSupported(format.ContentType);
}
@@ -640,14 +639,14 @@ protected virtual void OnWriteResponseHeaders(
if (resultKind is ResultKind.Stream or ResultKind.Single
&& start.Kind is MultiPartMixed or AllMultiPart or All)
{
- // if the result is a stream we consider this a perfect match and
+ // if the result is a stream, we consider this a perfect match and
// will use this format.
if (resultKind is ResultKind.Stream)
{
possibleFormat = _multiPartFormat;
}
- // if the format is an event-stream or not set we will create a
+ // if the format is an event-stream or not set, we will create a
// multipart/mixed formatInfo for the current result but also keep
// on validating for a better suited format.
if (possibleFormat?.Kind is not ResponseContentType.Json)
@@ -658,17 +657,17 @@ protected virtual void OnWriteResponseHeaders(
if (start.Kind is EventStream or All)
{
- // if the result is a subscription we consider this a perfect match and
+ // if the result is a subscription, we consider this a perfect match and
// will use this format.
if (resultKind is ResultKind.Subscription or ResultKind.Stream)
{
possibleFormat = _eventStreamFormat;
}
- // if the result is stream it means that we did not yet validate a
+ // if the result is stream, it means that we did not yet validate a
// multipart content-type and thus will create a format for the case that it
// is not specified;
- // or we have a single result but there is no format yet specified
+ // or we have a single result, but there is no format yet specified
// we will create a text/event-stream formatInfo for the current result
// but also keep on validating for a better suited format.
if (possibleFormat?.Kind is ResponseContentType.Unknown)
@@ -686,7 +685,10 @@ protected virtual void OnWriteResponseHeaders(
internal static DefaultHttpResponseFormatter Create(
HttpResponseFormatterOptions options,
ITimeProvider timeProvider)
- => new SealedDefaultHttpResponseFormatter(options, timeProvider);
+ {
+ ArgumentNullException.ThrowIfNull(timeProvider);
+ return new SealedDefaultHttpResponseFormatter(options, timeProvider);
+ }
///
/// Representation of a resolver format, containing the formatter and the content type.
@@ -767,7 +769,7 @@ private static string CreateETag(byte[] schema, ulong version)
}
private static string GetSchemaFileName(ISchemaDefinition schema)
- => schema.Name.EqualsOrdinal(ISchemaDefinition.DefaultName)
+ => schema.Name.Equals(ISchemaDefinition.DefaultName, StringComparison.OrdinalIgnoreCase)
? "schema.graphql"
: schema.Name + ".schema.graphql";
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultWebSocketPayloadFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultWebSocketPayloadFormatter.cs
similarity index 75%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultWebSocketPayloadFormatter.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultWebSocketPayloadFormatter.cs
index bc494643f2b..f7eda176d65 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultWebSocketPayloadFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/DefaultWebSocketPayloadFormatter.cs
@@ -1,12 +1,12 @@
using System.Text.Json;
using HotChocolate.Transport.Formatters;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Formatters;
///
/// This represents the default implementation for the .
///
-public class DefaultWebSocketPayloadFormatter(WebSocketPayloadFormatterOptions options)
+public class DefaultWebSocketPayloadFormatter(WebSocketPayloadFormatterOptions options = default)
: IWebSocketPayloadFormatter
{
private readonly JsonResultFormatter _jsonFormatter = new(options.Json);
@@ -28,4 +28,10 @@ public void Format(IReadOnlyList errors, Utf8JsonWriter jsonWriter)
{
_jsonFormatter.FormatErrors(errors, jsonWriter);
}
+
+ ///
+ public void Format(IReadOnlyDictionary extensions, Utf8JsonWriter jsonWriter)
+ {
+ _jsonFormatter.FormatDictionary(extensions, jsonWriter);
+ }
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResponseFormatterOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/HttpResponseFormatterOptions.cs
similarity index 90%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResponseFormatterOptions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/HttpResponseFormatterOptions.cs
index bfbab44b7fb..2cf58892cc9 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResponseFormatterOptions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/HttpResponseFormatterOptions.cs
@@ -1,6 +1,6 @@
using HotChocolate.Transport.Formatters;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Formatters;
///
/// Represents the GraphQL over HTTP formatter options.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpResponseFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs
similarity index 97%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpResponseFormatter.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs
index 4c50c47a247..40f6305ea0b 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpResponseFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IHttpResponseFormatter.cs
@@ -1,7 +1,7 @@
using System.Net;
using Microsoft.AspNetCore.Http;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Formatters;
///
/// This interface specifies how a GraphQL result is formatted to a HTTP response.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IWebSocketPayloadFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IWebSocketPayloadFormatter.cs
similarity index 62%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IWebSocketPayloadFormatter.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IWebSocketPayloadFormatter.cs
index ebc5dbf38da..6833e680501 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IWebSocketPayloadFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/IWebSocketPayloadFormatter.cs
@@ -1,6 +1,6 @@
using System.Text.Json;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Formatters;
///
/// This interface specifies how a GraphQL result is formatted as a WebSocket payload.
@@ -8,7 +8,7 @@ namespace HotChocolate.AspNetCore.Serialization;
public interface IWebSocketPayloadFormatter
{
///
- /// Formats the given into a WebSocket payload.
+ /// Formats the into a WebSocket payload.
///
///
/// The GraphQL operation result.
@@ -19,7 +19,7 @@ public interface IWebSocketPayloadFormatter
void Format(IOperationResult result, Utf8JsonWriter jsonWriter);
///
- /// Formats the given into a WebSocket payload.
+ /// Formats the into a WebSocket payload.
///
///
/// The GraphQL execution error.
@@ -30,7 +30,7 @@ public interface IWebSocketPayloadFormatter
void Format(IError error, Utf8JsonWriter jsonWriter);
///
- /// Formats the given into a WebSocket payload.
+ /// Formats the into a WebSocket payload.
///
///
/// The GraphQL execution errors.
@@ -39,4 +39,15 @@ public interface IWebSocketPayloadFormatter
/// The JSON writer that is used to write the errors.
///
void Format(IReadOnlyList errors, Utf8JsonWriter jsonWriter);
+
+ ///
+ /// Formats the into a WebSocket payload.
+ ///
+ ///
+ /// The GraphQL extensions.
+ ///
+ ///
+ /// The JSON writer that is used to write the extensions.
+ ///
+ void Format(IReadOnlyDictionary extensions, Utf8JsonWriter jsonWriter);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/WebSocketPayloadFormatterOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/WebSocketPayloadFormatterOptions.cs
similarity index 87%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/WebSocketPayloadFormatterOptions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/WebSocketPayloadFormatterOptions.cs
index bfdf3e6f617..1badff96d4c 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/WebSocketPayloadFormatterOptions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Formatters/WebSocketPayloadFormatterOptions.cs
@@ -1,6 +1,6 @@
using HotChocolate.Transport.Formatters;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Formatters;
///
/// Represents the GraphQL over WebSocket payload formatter options.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLRequestException.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/GraphQLRequestException.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLRequestException.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/GraphQLRequestException.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HotChocolate.AspNetCore.Pipeline.csproj b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HotChocolate.AspNetCore.Pipeline.csproj
new file mode 100644
index 00000000000..fcd1760b60f
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HotChocolate.AspNetCore.Pipeline.csproj
@@ -0,0 +1,59 @@
+
+
+
+ HotChocolate.AspNetCore.Pipeline
+ HotChocolate.AspNetCore.Pipeline
+ HotChocolate.AspNetCore
+ This package contains the GraphQL ASP.NET Core middleware for Hot Chocolate.
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ AspNetCorePipelineResources.Designer.cs
+
+
+
+
+
+ True
+ True
+ AspNetCorePipelineResources.resx
+
+
+
+
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetMiddleware.cs
similarity index 60%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetMiddleware.cs
index f45aa95c314..2c99a3710e5 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetMiddleware.cs
@@ -1,37 +1,26 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using static HotChocolate.AspNetCore.Serialization.DefaultHttpRequestParser;
+using HotChocolate.AspNetCore.Utilities;
+using static HotChocolate.AspNetCore.Parsers.DefaultHttpRequestParser;
using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
namespace HotChocolate.AspNetCore;
public sealed class HttpGetMiddleware : MiddlewareBase
{
- private readonly IHttpRequestParser _requestParser;
- private readonly IServerDiagnosticEvents _diagnosticEvents;
-
public HttpGetMiddleware(
HttpRequestDelegate next,
- IRequestExecutorProvider executorResolver,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IHttpRequestParser requestParser,
- IServerDiagnosticEvents diagnosticEvents,
- string schemaName)
- : base(next, executorResolver, executorEvents, responseFormatter, schemaName)
+ HttpRequestExecutorProxy executor)
+ : base(next, executor)
{
- _requestParser = requestParser ??
- throw new ArgumentNullException(nameof(requestParser));
- _diagnosticEvents = diagnosticEvents ??
- throw new ArgumentNullException(nameof(diagnosticEvents));
}
public async Task InvokeAsync(HttpContext context)
{
if (HttpMethods.IsGet(context.Request.Method))
{
+ var session = await Executor.GetOrCreateSessionAsync(context.RequestAborted);
var options = GetOptions(context);
if (options.EnableGetRequests &&
@@ -50,16 +39,11 @@ public async Task InvokeAsync(HttpContext context)
// Allow HTTP GraphQL GET requests if the content type is set to
// application/json.
- ParseContentType(context) is RequestContentType.Json))
+ context.ParseContentType() is RequestContentType.Json))
{
- if (!IsDefaultSchema)
- {
- context.Items[WellKnownContextData.SchemaName] = SchemaName;
- }
-
- using (_diagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpGet))
+ using (session.DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpGet))
{
- await HandleRequestAsync(context);
+ await HandleRequestAsync(context, session);
}
return;
@@ -71,17 +55,13 @@ public async Task InvokeAsync(HttpContext context)
await NextAsync(context);
}
- private async Task HandleRequestAsync(HttpContext context)
+ private async Task HandleRequestAsync(HttpContext context, ExecutorSession session)
{
HttpStatusCode? statusCode;
IExecutionResult? result;
// first we validate the accept headers.
- var validationResult =
- MiddlewareHelper.ValidateAcceptContentType(
- context,
- ResponseFormatter,
- _diagnosticEvents);
+ var validationResult = MiddlewareHelper.ValidateAcceptContentType(context, session);
var acceptMediaTypes = validationResult.AcceptMediaTypes;
@@ -93,16 +73,7 @@ private async Task HandleRequestAsync(HttpContext context)
}
// next we parse the GraphQL request.
- var executor = ExecutorProxy.CurrentExecutor ??
- await ExecutorProxy.GetExecutorAsync(context.RequestAborted);
- var errorHandler = executor.GetErrorHandler();
-
- var parserResult =
- MiddlewareHelper.ParseRequestFromParams(
- context,
- _requestParser,
- errorHandler,
- _diagnosticEvents);
+ var parserResult = MiddlewareHelper.ParseRequestFromParams(context, session);
if (!parserResult.IsValid)
{
@@ -125,9 +96,7 @@ await MiddlewareHelper.ExecuteRequestAsync(
request,
requestFlags,
context,
- executor,
- errorHandler,
- _diagnosticEvents);
+ session);
statusCode = executionResult.StatusCode;
result = executionResult.Result;
@@ -137,7 +106,6 @@ await MiddlewareHelper.WriteResultAsync(
acceptMediaTypes,
statusCode,
context,
- ResponseFormatter,
- _diagnosticEvents);
+ session);
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetSchemaMiddleware.cs
similarity index 65%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetSchemaMiddleware.cs
index ef48789af32..40d411a2db4 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpGetSchemaMiddleware.cs
@@ -1,11 +1,8 @@
+using System.Text;
using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using HotChocolate.Execution.Options;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
using static System.Net.HttpStatusCode;
-using static HotChocolate.AspNetCore.ErrorHelper;
-using static HotChocolate.SchemaPrinter;
+using static HotChocolate.AspNetCore.Utilities.ErrorHelper;
using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
namespace HotChocolate.AspNetCore;
@@ -22,21 +19,15 @@ public sealed class HttpGetSchemaMiddleware : MiddlewareBase
];
private readonly MiddlewareRoutingType _routing;
- private readonly IServerDiagnosticEvents _diagnosticEvents;
private readonly PathString _path;
public HttpGetSchemaMiddleware(
HttpRequestDelegate next,
- IRequestExecutorProvider executorProvider,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IServerDiagnosticEvents diagnosticEvents,
- string schemaName,
+ HttpRequestExecutorProxy executor,
PathString path,
MiddlewareRoutingType routing)
- : base(next, executorProvider, executorEvents, responseFormatter, schemaName)
+ : base(next, executor)
{
- _diagnosticEvents = diagnosticEvents ?? throw new ArgumentNullException(nameof(diagnosticEvents));
_path = path;
_routing = routing;
}
@@ -52,14 +43,12 @@ public async Task InvokeAsync(HttpContext context)
if (handle)
{
- if (!IsDefaultSchema)
- {
- context.Items[WellKnownContextData.SchemaName] = SchemaName;
- }
+ var session = await Executor.GetOrCreateSessionAsync(context.RequestAborted);
+ var options = GetOptions(context);
- using (_diagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpGetSchema))
+ using (session.DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpGetSchema))
{
- await HandleRequestAsync(context);
+ await HandleRequestAsync(context, session, options);
}
}
else
@@ -82,12 +71,8 @@ private bool IsSchemaPath(HttpRequest request)
return false;
}
- private async Task HandleRequestAsync(HttpContext context)
+ private async Task HandleRequestAsync(HttpContext context, ExecutorSession session, GraphQLServerOptions options)
{
- var executor = await GetExecutorAsync(context.RequestAborted);
- context.Items[WellKnownContextData.Schema] = executor.Schema;
- var options = executor.Schema.Services.GetRequiredService();
-
if (!options.EnableSchemaFileSupport)
{
context.Response.StatusCode = 404;
@@ -100,7 +85,7 @@ private async Task HandleRequestAsync(HttpContext context)
if (string.IsNullOrEmpty(s))
{
- await WriteResultAsync(
+ await session.WriteResultAsync(
context,
TypeNameIsEmpty(),
s_mediaTypes,
@@ -108,17 +93,17 @@ await WriteResultAsync(
return;
}
- await WriteTypesAsync(context, executor.Schema, s, true);
+ await WriteTypesAsync(context, session, s, true);
}
else
{
- await WriteSchemaAsync(context, executor);
+ await session.WriteSchemaAsync(context);
}
}
private async Task WriteTypesAsync(
HttpContext context,
- ISchemaDefinition schema,
+ ExecutorSession session,
string typeNames,
bool indent)
{
@@ -130,7 +115,7 @@ private async Task WriteTypesAsync(
|| coordinate.Value.MemberName is not null
|| coordinate.Value.ArgumentName is not null)
{
- await WriteResultAsync(
+ await session.WriteResultAsync(
context,
InvalidTypeName(typeName),
s_mediaTypes,
@@ -138,9 +123,9 @@ await WriteResultAsync(
return;
}
- if (!schema.Types.TryGetType(coordinate.Value.Name, out var type))
+ if (!session.Schema.Types.TryGetType(coordinate.Value.Name, out var type))
{
- await WriteResultAsync(
+ await session.WriteResultAsync(
context,
TypeNotFound(typeName),
s_mediaTypes,
@@ -153,20 +138,33 @@ await WriteResultAsync(
context.Response.ContentType = ContentType.GraphQL;
context.Response.Headers.SetContentDisposition(GetTypesFileName(types));
- await PrintAsync(types, context.Response.Body, indent, context.RequestAborted);
+ await PrintTypesAsync(types, context.Response.Body, indent, context.RequestAborted);
}
- private ValueTask WriteSchemaAsync(
- HttpContext context,
- IRequestExecutor executor)
- => ResponseFormatter.FormatAsync(
- context.Response,
- executor.Schema,
- executor.Version,
- context.RequestAborted);
-
private string GetTypesFileName(List types)
=> types.Count == 1
? $"{types[0].Name}.graphql"
: "types.graphql";
+
+ private static async Task PrintTypesAsync(
+ List types,
+ Stream stream,
+ bool indent,
+ CancellationToken cancellationToken)
+ {
+ var next = false;
+ await using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, leaveOpen: true);
+
+ foreach (var type in types)
+ {
+ if (next)
+ {
+ await streamWriter.WriteLineAsync();
+ }
+
+ var s = type.ToSyntaxNode().ToString(indent);
+ await streamWriter.WriteLineAsync(s.AsMemory(), cancellationToken);
+ next = true;
+ }
+ }
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpHeaderKeys.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpHeaderKeys.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpHeaderValues.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpHeaderValues.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpMultipartMiddleware.cs
similarity index 80%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpMultipartMiddleware.cs
index 754a38a6779..20a78066f0c 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpMultipartMiddleware.cs
@@ -1,15 +1,17 @@
using System.Text.Json;
+using System.Text.Json.Serialization;
+using HotChocolate.AspNetCore.Instrumentation;
+using HotChocolate.AspNetCore.Parsers;
+using HotChocolate.AspNetCore.Utilities;
+using HotChocolate.Language;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Options;
-using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using HotChocolate.Language;
-using HotChocolate.Utilities;
using static System.Net.HttpStatusCode;
-using static HotChocolate.AspNetCore.ErrorHelper;
-using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;
+using static HotChocolate.AspNetCore.Utilities.ErrorHelper;
+using static HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources;
using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
+using ThrowHelper = HotChocolate.AspNetCore.Utilities.ThrowHelper;
namespace HotChocolate.AspNetCore;
@@ -22,22 +24,11 @@ public sealed class HttpMultipartMiddleware : HttpPostMiddlewareBase
public HttpMultipartMiddleware(
HttpRequestDelegate next,
- IRequestExecutorProvider executorProvider,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IHttpRequestParser requestParser,
- IServerDiagnosticEvents diagnosticEvents,
- string schemaName,
+ HttpRequestExecutorProxy executor,
IOptions formOptions)
- : base(
- next,
- executorProvider,
- executorEvents,
- responseFormatter,
- requestParser,
- diagnosticEvents,
- schemaName)
+ : base(next, executor)
{
+ ArgumentNullException.ThrowIfNull(formOptions);
_formOptions = formOptions.Value;
}
@@ -45,24 +36,21 @@ public override async Task InvokeAsync(HttpContext context)
{
if (HttpMethods.IsPost(context.Request.Method) &&
GetOptions(context).EnableMultipartRequests &&
- ParseContentType(context) == RequestContentType.Form)
+ context.ParseContentType() == RequestContentType.Form)
{
+ var session = await Executor.GetOrCreateSessionAsync(context.RequestAborted);
+
if (!context.Request.Headers.ContainsKey(HttpHeaderKeys.Preflight) &&
GetOptions(context).EnforceMultipartRequestsPreflightHeader)
{
var headerResult = HeaderUtilities.GetAcceptHeader(context.Request);
- await WriteResultAsync(context, _multipartRequestError, headerResult.AcceptMediaTypes, BadRequest);
+ await session.WriteResultAsync(context, _multipartRequestError, headerResult.AcceptMediaTypes, BadRequest);
return;
}
- if (!IsDefaultSchema)
+ using (session.DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpMultiPart))
{
- context.Items[WellKnownContextData.SchemaName] = SchemaName;
- }
-
- using (DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpMultiPart))
- {
- await HandleRequestAsync(context);
+ await HandleRequestAsync(context, session, context.RequestAborted);
}
}
else
@@ -74,15 +62,16 @@ public override async Task InvokeAsync(HttpContext context)
}
protected override async ValueTask> ParseRequestsFromBodyAsync(
- HttpRequest httpRequest,
- CancellationToken cancellationToken)
+ HttpContext context,
+ ExecutorSession session)
{
IFormCollection? form;
+ var httpRequest = context.Request;
try
{
var formFeature = new FormFeature(httpRequest, _formOptions);
- form = await formFeature.ReadFormAsync(cancellationToken);
+ form = await formFeature.ReadFormAsync(context.RequestAborted);
}
catch (Exception exception)
{
@@ -91,7 +80,7 @@ protected override async ValueTask> ParseRequestsF
// Parse the string values of interest from the IFormCollection
var multipartRequest = ParseMultipartRequest(form);
- var requests = RequestParser.ParseRequest(
+ var requests = session.RequestParser.ParseRequest(
multipartRequest.Operations);
foreach (var graphQLRequest in requests)
@@ -127,7 +116,7 @@ private static HttpMultipartRequest ParseMultipartRequest(IFormCollection form)
try
{
- map = JsonSerializer.Deserialize>(mapString!);
+ map = JsonSerializer.Deserialize(mapString!, FormsMapJsonContext.Default.Map);
}
catch
{
@@ -152,7 +141,7 @@ private static HttpMultipartRequest ParseMultipartRequest(IFormCollection form)
return new HttpMultipartRequest(operations, pathToFileMap);
}
- private static IDictionary MapFilesToObjectPaths(
+ private static Dictionary MapFilesToObjectPaths(
IDictionary map,
IFormFileCollection files)
{
@@ -165,9 +154,7 @@ private static IDictionary MapFilesToObjectPaths(
throw ThrowHelper.HttpMultipartMiddleware_NoObjectPath(filename);
}
- var file = filename is { Length: > 0 }
- ? files.GetFile(filename)
- : null;
+ var file = filename.Length > 0 ? files.GetFile(filename) : null;
if (file is null)
{
@@ -233,7 +220,7 @@ private static IValueNode RewriteVariable(
for (var i = 0; i < ov.Fields.Count; i++)
{
- if (ov.Fields[i].Name.Value.EqualsOrdinal(key.Value))
+ if (ov.Fields[i].Name.Value.Equals(key.Value, StringComparison.Ordinal))
{
pos = i;
break;
@@ -267,3 +254,6 @@ key.Next is not null
throw ThrowHelper.HttpMultipartMiddleware_VariableNotFound(objectPath);
}
}
+
+[JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "Map")]
+internal partial class FormsMapJsonContext : JsonSerializerContext;
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpPostMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpPostMiddleware.cs
new file mode 100644
index 00000000000..75aeb867b4c
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpPostMiddleware.cs
@@ -0,0 +1,6 @@
+using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
+
+namespace HotChocolate.AspNetCore;
+
+public sealed class HttpPostMiddleware(HttpRequestDelegate next, HttpRequestExecutorProxy executor)
+ : HttpPostMiddlewareBase(next, executor);
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddlewareBase.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpPostMiddlewareBase.cs
similarity index 67%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddlewareBase.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpPostMiddlewareBase.cs
index de284fef6d8..010faec00fb 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddlewareBase.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpPostMiddlewareBase.cs
@@ -3,7 +3,7 @@
using System.Net;
using System.Text;
using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
+using HotChocolate.AspNetCore.Utilities;
using HotChocolate.Language;
using Microsoft.AspNetCore.Http;
using static HotChocolate.Execution.RequestFlags;
@@ -11,43 +11,28 @@
namespace HotChocolate.AspNetCore;
-public class HttpPostMiddlewareBase : MiddlewareBase
+public abstract class HttpPostMiddlewareBase : MiddlewareBase
{
private const string BatchOperations = "batchOperations";
protected HttpPostMiddlewareBase(
HttpRequestDelegate next,
- IRequestExecutorProvider executorResolver,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IHttpRequestParser requestParser,
- IServerDiagnosticEvents diagnosticEvents,
- string schemaName)
- : base(next, executorResolver, executorEvents, responseFormatter, schemaName)
+ HttpRequestExecutorProxy executor)
+ : base(next, executor)
{
- RequestParser = requestParser ??
- throw new ArgumentNullException(nameof(requestParser));
- DiagnosticEvents = diagnosticEvents ??
- throw new ArgumentNullException(nameof(diagnosticEvents));
}
- protected IHttpRequestParser RequestParser { get; }
-
- protected IServerDiagnosticEvents DiagnosticEvents { get; }
-
public virtual async Task InvokeAsync(HttpContext context)
{
- if (HttpMethods.IsPost(context.Request.Method) &&
- ParseContentType(context) is RequestContentType.Json)
+ if (HttpMethods.IsPost(context.Request.Method)
+ && context.ParseContentType() is RequestContentType.Json)
{
- if (!IsDefaultSchema)
- {
- context.Items[WellKnownContextData.SchemaName] = SchemaName;
- }
+ var ct = context.RequestAborted;
+ var session = await Executor.GetOrCreateSessionAsync(ct);
- using (DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpPost))
+ using (session.DiagnosticEvents.ExecuteHttpRequest(context, HttpRequestKind.HttpPost))
{
- await HandleRequestAsync(context);
+ await HandleRequestAsync(context, session, ct);
}
}
else
@@ -58,17 +43,11 @@ public virtual async Task InvokeAsync(HttpContext context)
}
}
- protected async Task HandleRequestAsync(HttpContext context)
+ protected async Task HandleRequestAsync(HttpContext context, ExecutorSession session, CancellationToken ct)
{
HttpStatusCode? statusCode = null;
IExecutionResult? result;
- // first we need to get the request executor to be able to execute requests.
- var requestExecutor = await GetExecutorAsync(context.RequestAborted);
- var requestInterceptor = requestExecutor.GetRequestInterceptor();
- var errorHandler = requestExecutor.GetErrorHandler();
- context.Items[WellKnownContextData.RequestExecutor] = requestExecutor;
-
// next we will inspect the accept headers and determine if we can execute this request.
var headerResult = HeaderUtilities.GetAcceptHeader(context.Request);
var acceptMediaTypes = headerResult.AcceptMediaTypes;
@@ -84,11 +63,11 @@ protected async Task HandleRequestAsync(HttpContext context)
var errors = headerResult.ErrorResult.Errors!;
result = headerResult.ErrorResult;
- DiagnosticEvents.HttpRequestError(context, errors[0]);
+ session.DiagnosticEvents.HttpRequestError(context, errors[0]);
goto HANDLE_RESULT;
}
- var requestFlags = CreateRequestFlags(headerResult.AcceptMediaTypes);
+ var requestFlags = session.CreateRequestFlags(headerResult.AcceptMediaTypes);
// if the request defines accept header values of which we cannot handle any provided
// media type then we will fail the request with 406 Not Acceptable.
@@ -101,18 +80,18 @@ protected async Task HandleRequestAsync(HttpContext context)
var error = ErrorHelper.NoSupportedAcceptMediaType();
result = OperationResultBuilder.CreateError(error);
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
goto HANDLE_RESULT;
}
// next we parse the GraphQL request.
IReadOnlyList requests;
- using (DiagnosticEvents.ParseHttpRequest(context))
+ using (session.DiagnosticEvents.ParseHttpRequest(context))
{
try
{
- requests = await ParseRequestsFromBodyAsync(context.Request, context.RequestAborted);
+ requests = await ParseRequestsFromBodyAsync(context, session);
}
catch (GraphQLRequestException ex)
{
@@ -120,9 +99,9 @@ protected async Task HandleRequestAsync(HttpContext context)
// parsed. In this case we will return HTTP status code 400 and return a
// GraphQL error result.
statusCode = HttpStatusCode.BadRequest;
- var errors = errorHandler.Handle(ex.Errors);
+ var errors = session.Handle(ex.Errors);
result = OperationResultBuilder.CreateError(errors);
- DiagnosticEvents.ParserErrors(context, errors);
+ session.DiagnosticEvents.ParserErrors(context, errors);
goto HANDLE_RESULT;
}
catch (Exception ex)
@@ -130,7 +109,7 @@ protected async Task HandleRequestAsync(HttpContext context)
statusCode = HttpStatusCode.InternalServerError;
var error = ErrorBuilder.FromException(ex).Build();
result = OperationResultBuilder.CreateError(error);
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
goto HANDLE_RESULT;
}
}
@@ -145,9 +124,9 @@ protected async Task HandleRequestAsync(HttpContext context)
case 0:
{
statusCode = HttpStatusCode.BadRequest;
- var error = errorHandler.Handle(ErrorHelper.RequestHasNoElements());
+ var error = session.Handle(ErrorHelper.RequestHasNoElements());
result = OperationResultBuilder.CreateError(error);
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
break;
}
@@ -166,21 +145,14 @@ protected async Task HandleRequestAsync(HttpContext context)
TryParseOperations(operationNames, out var ops) &&
GetOptions(context).EnableBatching)
{
- result = await ExecuteOperationBatchAsync(
- context,
- requestExecutor,
- requestInterceptor,
- DiagnosticEvents,
- requests[0],
- requestFlags,
- ops);
+ result = await session.ExecuteOperationBatchAsync(context, requests[0], requestFlags, ops);
}
else
{
- var error = errorHandler.Handle(ErrorHelper.InvalidRequest());
+ var error = session.Handle(ErrorHelper.InvalidRequest());
statusCode = HttpStatusCode.BadRequest;
result = OperationResultBuilder.CreateError(error);
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
}
break;
@@ -194,13 +166,7 @@ protected async Task HandleRequestAsync(HttpContext context)
// a single GraphQL query or mutation.
case 1:
{
- result = await ExecuteSingleAsync(
- context,
- requestExecutor,
- requestInterceptor,
- DiagnosticEvents,
- requests[0],
- requestFlags);
+ result = await session.ExecuteSingleAsync(context, requests[0], requestFlags);
break;
}
@@ -210,20 +176,14 @@ protected async Task HandleRequestAsync(HttpContext context)
default:
if (GetOptions(context).EnableBatching)
{
- result = await ExecuteBatchAsync(
- context,
- requestExecutor,
- requestInterceptor,
- DiagnosticEvents,
- requests,
- requestFlags);
+ result = await session.ExecuteBatchAsync(context, requests, requestFlags);
}
else
{
- var error = errorHandler.Handle(ErrorHelper.InvalidRequest());
+ var error = session.Handle(ErrorHelper.InvalidRequest());
statusCode = HttpStatusCode.BadRequest;
result = OperationResultBuilder.CreateError(error);
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
}
break;
}
@@ -236,7 +196,7 @@ protected async Task HandleRequestAsync(HttpContext context)
foreach (var error in ex.Errors)
{
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
}
}
catch (Exception ex)
@@ -244,7 +204,7 @@ protected async Task HandleRequestAsync(HttpContext context)
statusCode = HttpStatusCode.InternalServerError;
var error = ErrorBuilder.FromException(ex).Build();
result = OperationResultBuilder.CreateError(error);
- DiagnosticEvents.HttpRequestError(context, error);
+ session.DiagnosticEvents.HttpRequestError(context, error);
}
HANDLE_RESULT:
@@ -254,7 +214,7 @@ protected async Task HandleRequestAsync(HttpContext context)
{
// if cancellation is requested we will not try to attempt to write the result to the
// response stream.
- if (context.RequestAborted.IsCancellationRequested)
+ if (ct.IsCancellationRequested)
{
return;
}
@@ -265,10 +225,10 @@ protected async Task HandleRequestAsync(HttpContext context)
if (result is IOperationResult queryResult)
{
- formatScope = DiagnosticEvents.FormatHttpResponse(context, queryResult);
+ formatScope = session.DiagnosticEvents.FormatHttpResponse(context, queryResult);
}
- await WriteResultAsync(context, result, acceptMediaTypes, statusCode);
+ await session.WriteResultAsync(context, result, acceptMediaTypes, statusCode);
}
finally
{
@@ -284,10 +244,10 @@ protected async Task HandleRequestAsync(HttpContext context)
}
}
- protected virtual ValueTask> ParseRequestsFromBodyAsync(
- HttpRequest request,
- CancellationToken cancellationToken)
- => RequestParser.ParseRequestAsync(request.Body, cancellationToken);
+ protected virtual async ValueTask> ParseRequestsFromBodyAsync(
+ HttpContext context,
+ ExecutorSession session)
+ => await session.RequestParser.ParseRequestAsync(context.Request.Body, context.RequestAborted);
private static bool TryParseOperations(
string operationNameString,
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpRequestExecutorProxy.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpRequestExecutorProxy.cs
new file mode 100644
index 00000000000..eceba55afff
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpRequestExecutorProxy.cs
@@ -0,0 +1,44 @@
+using HotChocolate.Features;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate.AspNetCore;
+
+public sealed class HttpRequestExecutorProxy(
+ IRequestExecutorProvider executorProvider,
+ IRequestExecutorEvents executorEvents,
+ string schemaName)
+ : RequestExecutorProxy(executorProvider, executorEvents, schemaName)
+{
+ private ExecutorSession? _session;
+
+ public async ValueTask GetOrCreateSessionAsync(CancellationToken cancellationToken)
+ {
+ if (_session is not null)
+ {
+ return _session;
+ }
+
+ var executor = await GetExecutorAsync(cancellationToken);
+ return executor.Features.GetRequired();
+ }
+
+ protected override void OnRequestExecutorUpdated(IRequestExecutor? executor)
+ {
+ if (executor is null)
+ {
+ _session = null;
+ return;
+ }
+
+ var session = new ExecutorSession(executor);
+ executor.Features.Set(session);
+ _session = session;
+ }
+
+ public static HttpRequestExecutorProxy Create(IServiceProvider services, string schemaName)
+ {
+ var executorProvider = services.GetRequiredService();
+ var executorEvents = services.GetRequiredService();
+ return new HttpRequestExecutorProxy(executorProvider, executorEvents, schemaName);
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpTransportVersion.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpTransportVersion.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HttpTransportVersion.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpTransportVersion.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/AggregateServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/AggregateServerDiagnosticEventListener.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/AggregateServerDiagnosticEventListener.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/AggregateServerDiagnosticEventListener.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/HttpRequestKind.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/HttpRequestKind.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/HttpRequestKind.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/HttpRequestKind.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/IServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEventListener.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/IServerDiagnosticEventListener.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEventListener.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/IServerDiagnosticEvents.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEvents.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/IServerDiagnosticEvents.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEvents.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/NoopServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/NoopServerDiagnosticEventListener.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/NoopServerDiagnosticEventListener.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/NoopServerDiagnosticEventListener.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/ServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/ServerDiagnosticEventListener.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Instrumentation/ServerDiagnosticEventListener.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/ServerDiagnosticEventListener.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DefaultHttpRequestInterceptor.cs
similarity index 91%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DefaultHttpRequestInterceptor.cs
index 9e84d365173..d00fabebeff 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DefaultHttpRequestInterceptor.cs
@@ -1,5 +1,6 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
+using static HotChocolate.ExecutionContextData;
namespace HotChocolate.AspNetCore;
@@ -28,7 +29,7 @@ public virtual ValueTask OnCreateAsync(
if (context.IncludeQueryPlan())
{
- requestBuilder.TryAddGlobalState(WellKnownContextData.IncludeQueryPlan, true);
+ requestBuilder.TryAddGlobalState(IncludeQueryPlan, true);
}
if (context.TryGetCostSwitch() is { } costSwitch)
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DefaultSocketSessionInterceptor.cs
similarity index 98%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DefaultSocketSessionInterceptor.cs
index 1f39484992d..ac884615557 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DefaultSocketSessionInterceptor.cs
@@ -3,7 +3,7 @@
using HotChocolate.AspNetCore.Subscriptions;
using HotChocolate.AspNetCore.Subscriptions.Protocols;
using Microsoft.Extensions.DependencyInjection;
-using static HotChocolate.WellKnownContextData;
+using static HotChocolate.ExecutionContextData;
namespace HotChocolate.AspNetCore;
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DelegateHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DelegateHttpRequestInterceptor.cs
new file mode 100644
index 00000000000..44504b2a80a
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/DelegateHttpRequestInterceptor.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Http;
+
+namespace HotChocolate.AspNetCore;
+
+internal sealed class DelegateHttpRequestInterceptor(
+ Func handler)
+ : DefaultHttpRequestInterceptor
+{
+ public override async ValueTask OnCreateAsync(HttpContext context, IRequestExecutor requestExecutor,
+ OperationRequestBuilder requestBuilder,
+ CancellationToken cancellationToken)
+ {
+ await handler(context, requestExecutor, requestBuilder, cancellationToken);
+ await base.OnCreateAsync(context, requestExecutor, requestBuilder, cancellationToken);
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/IHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/IHttpRequestInterceptor.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/IHttpRequestInterceptor.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/IHttpRequestInterceptor.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ISocketSessionInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/ISocketSessionInterceptor.cs
similarity index 98%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/ISocketSessionInterceptor.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/ISocketSessionInterceptor.cs
index d9af946aab2..6945d03bca1 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/ISocketSessionInterceptor.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Interceptors/ISocketSessionInterceptor.cs
@@ -4,7 +4,7 @@
namespace HotChocolate.AspNetCore;
///
-/// The socket interceptor allows to customize the GraphQL over Websocket behavior.
+/// The socket interceptor allows customizing the GraphQL over Websocket behavior.
///
public interface ISocketSessionInterceptor
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareBase.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareBase.cs
new file mode 100644
index 00000000000..82288ce5a71
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareBase.cs
@@ -0,0 +1,64 @@
+using Microsoft.AspNetCore.Http;
+using RequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
+
+namespace HotChocolate.AspNetCore;
+
+///
+/// The Hot Chocolate ASP.NET core middleware base class.
+///
+public abstract class MiddlewareBase : IDisposable
+{
+ private readonly RequestDelegate _next;
+ private GraphQLServerOptions? _options;
+ private bool _disposed;
+
+ protected MiddlewareBase(
+ RequestDelegate next,
+ HttpRequestExecutorProxy executor)
+ {
+ ArgumentNullException.ThrowIfNull(next);
+ ArgumentNullException.ThrowIfNull(executor);
+
+ _next = next;
+ Executor = executor;
+ }
+
+ ///
+ /// Gets the request executor proxy.
+ ///
+ protected HttpRequestExecutorProxy Executor { get; }
+
+ ///
+ /// Invokes the next middleware in line.
+ ///
+ ///
+ /// The .
+ ///
+ protected Task NextAsync(HttpContext context) => _next(context);
+
+ protected GraphQLServerOptions GetOptions(HttpContext context)
+ {
+ if (_options is not null)
+ {
+ return _options;
+ }
+
+ _options = context.GetGraphQLServerOptions() ?? new GraphQLServerOptions();
+ return _options;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed && disposing)
+ {
+ Executor.Dispose();
+ _disposed = true;
+ }
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs
new file mode 100644
index 00000000000..4be3a60b7a5
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareFactory.cs
@@ -0,0 +1,77 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Options;
+using RequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
+
+namespace HotChocolate.AspNetCore;
+
+internal static class MiddlewareFactory
+{
+ internal static Func CreateCancellationMiddleware()
+ {
+ return next => async context =>
+ {
+ try
+ {
+ await next(context);
+ }
+ catch (OperationCanceledException)
+ {
+ // we just catch cancellations here and do nothing.
+ }
+ };
+ }
+
+ internal static Func CreateWebSocketSubscriptionMiddleware(
+ HttpRequestExecutorProxy executor)
+ {
+ return next => context =>
+ {
+ var middleware = new WebSocketSubscriptionMiddleware(next, executor);
+ return middleware.InvokeAsync(context);
+ };
+ }
+
+ internal static Func CreateHttpPostMiddleware(
+ HttpRequestExecutorProxy executor)
+ {
+ return next => context =>
+ {
+ var middleware = new HttpPostMiddleware(next, executor);
+ return middleware.InvokeAsync(context);
+ };
+ }
+
+ internal static Func CreateHttpMultipartMiddleware(
+ HttpRequestExecutorProxy executor,
+ IOptions formOptions)
+ {
+ return next => context =>
+ {
+ var middleware = new HttpMultipartMiddleware(next, executor, formOptions);
+ return middleware.InvokeAsync(context);
+ };
+ }
+
+ internal static Func CreateHttpGetMiddleware(
+ HttpRequestExecutorProxy executor)
+ {
+ return next => context =>
+ {
+ var middleware = new HttpGetMiddleware(next, executor);
+ return middleware.InvokeAsync(context);
+ };
+ }
+
+ internal static Func CreateHttpGetSchemaMiddleware(
+ HttpRequestExecutorProxy executor,
+ PathString path,
+ MiddlewareRoutingType routingType)
+ {
+ return next => context =>
+ {
+ var middleware = new HttpGetSchemaMiddleware(next, executor, path, routingType);
+ return middleware.InvokeAsync(context);
+ };
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareRoutingType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareRoutingType.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareRoutingType.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/MiddlewareRoutingType.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLHttpOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLHttpOptions.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLHttpOptions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLHttpOptions.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLServerOptions.cs
similarity index 89%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLServerOptions.cs
index 168945629ef..c603a0f87eb 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLServerOptions.cs
@@ -21,6 +21,11 @@ public sealed class GraphQLServerOptions
public AllowedGetOperations AllowedGetOperations { get; set; } =
AllowedGetOperations.Query;
+ ///
+ /// Specifies that the transport is allowed to provide the schema SDL document as a file.
+ ///
+ public bool EnableSchemaFileSupport { get; set; } = true;
+
///
/// Defines if GraphQL HTTP GET requests are allowed.
///
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLSocketOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLSocketOptions.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLSocketOptions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLSocketOptions.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLToolOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLToolOptions.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLToolOptions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLToolOptions.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLToolServeMode.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLToolServeMode.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLToolServeMode.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Options/GraphQLToolServeMode.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpRequestParser.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/DefaultHttpRequestParser.cs
similarity index 81%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpRequestParser.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/DefaultHttpRequestParser.cs
index de6b88f645b..49e7e04f995 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpRequestParser.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/DefaultHttpRequestParser.cs
@@ -1,13 +1,15 @@
// ReSharper disable RedundantSuppressNullableWarningExpression
using System.Buffers;
-using Microsoft.AspNetCore.Http;
+using System.Text;
+using HotChocolate.AspNetCore.Utilities;
+using HotChocolate.Buffers;
using HotChocolate.Language;
-using HotChocolate.Utilities;
-using static HotChocolate.AspNetCore.ThrowHelper;
+using Microsoft.AspNetCore.Http;
+using static HotChocolate.AspNetCore.Utilities.ThrowHelper;
using static HotChocolate.Language.Utf8GraphQLRequestParser;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Parsers;
internal sealed class DefaultHttpRequestParser : IHttpRequestParser
{
@@ -18,6 +20,7 @@ internal sealed class DefaultHttpRequestParser : IHttpRequestParser
private const string VariablesKey = "variables";
internal const string ExtensionsKey = "extensions";
+ private static readonly Encoding s_utf8 = Encoding.UTF8;
private readonly IDocumentCache _documentCache;
private readonly IDocumentHashProvider _documentHashProvider;
private readonly ParserOptions _parserOptions;
@@ -54,24 +57,28 @@ public async ValueTask ParsePersistedOperationRequestAsync(
try
{
- GraphQLRequest Parse(byte[] buffer, int length)
- => ParsePersistedOperationRequest(buffer, length, documentId, operationName);
-
- return await BufferHelper.ReadAsync(
- requestBody,
- Parse,
- _maxRequestSize,
- static (buffer, bytesBuffered, p) =>
+ const int chunkSize = 256;
+ using var writer = new PooledArrayWriter();
+ var read = 0;
+
+ do
+ {
+ var memory = writer.GetMemory(chunkSize);
+ read = await requestBody.ReadAsync(memory, cancellationToken).ConfigureAwait(false);
+ writer.Advance(read);
+
+ if (_maxRequestSize < writer.Length)
{
- if (bytesBuffered == 0)
- {
- throw DefaultHttpRequestParser_RequestIsEmpty();
- }
-
- return p(buffer, bytesBuffered);
- },
- static () => throw DefaultHttpRequestParser_MaxRequestSizeExceeded(),
- cancellationToken);
+ throw DefaultHttpRequestParser_MaxRequestSizeExceeded();
+ }
+ } while (read == chunkSize);
+
+ if (writer.Length == 0)
+ {
+ throw DefaultHttpRequestParser_RequestIsEmpty();
+ }
+
+ return ParsePersistedOperationRequest(writer.WrittenSpan, documentId, operationName);
}
catch (GraphQLRequestException)
{
@@ -216,7 +223,9 @@ public GraphQLRequest ParsePersistedOperationRequestFromParams(
? stackalloc byte[length]
: source = ArrayPool.Shared.Rent(length);
- Utf8GraphQLParser.ConvertToBytes(sourceText, ref sourceSpan);
+ var written = Encoding.UTF8.GetBytes(sourceText, sourceSpan);
+ sourceSpan = sourceSpan[..written];
+
var document = Utf8GraphQLParser.Parse(sourceSpan, _parserOptions);
var documentHash = _documentHashProvider.ComputeHash(sourceSpan);
@@ -248,23 +257,28 @@ private async ValueTask> ReadAsync(
{
try
{
- Func> parse = ParseRequest;
+ const int chunkSize = 256;
+ using var writer = new PooledArrayWriter();
+ int read;
+
+ do
+ {
+ var memory = writer.GetMemory(chunkSize);
+ read = await stream.ReadAsync(memory, cancellationToken).ConfigureAwait(false);
+ writer.Advance(read);
- return await BufferHelper.ReadAsync(
- stream,
- parse,
- _maxRequestSize,
- static (buffer, bytesBuffered, p) =>
+ if (_maxRequestSize < writer.Length)
{
- if (bytesBuffered == 0)
- {
- throw DefaultHttpRequestParser_RequestIsEmpty();
- }
-
- return p(buffer, bytesBuffered);
- },
- static () => throw DefaultHttpRequestParser_MaxRequestSizeExceeded(),
- cancellationToken);
+ throw DefaultHttpRequestParser_MaxRequestSizeExceeded();
+ }
+ } while (read == chunkSize);
+
+ if (writer.Length == 0)
+ {
+ throw DefaultHttpRequestParser_RequestIsEmpty();
+ }
+
+ return ParseRequest(writer.WrittenSpan);
}
catch (GraphQLRequestException)
{
@@ -281,14 +295,10 @@ private async ValueTask> ReadAsync(
}
private IReadOnlyList ParseRequest(
- byte[] buffer,
- int bytesBuffered)
+ ReadOnlySpan request)
{
- var graphQLData = new ReadOnlySpan(buffer);
- graphQLData = graphQLData[..bytesBuffered];
-
var requestParser = new Utf8GraphQLRequestParser(
- graphQLData,
+ request,
_parserOptions,
_documentCache,
_documentHashProvider);
@@ -297,16 +307,12 @@ private IReadOnlyList ParseRequest(
}
private GraphQLRequest ParsePersistedOperationRequest(
- byte[] buffer,
- int bytesBuffered,
+ ReadOnlySpan request,
string documentId,
string? operationName)
{
- var graphQLData = new ReadOnlySpan(buffer);
- graphQLData = graphQLData[..bytesBuffered];
-
var requestParser = new Utf8GraphQLRequestParser(
- graphQLData,
+ request,
_parserOptions,
_documentCache,
_documentHashProvider);
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/HttpMultipartRequest.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/HttpMultipartRequest.cs
new file mode 100644
index 00000000000..414797d254f
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/HttpMultipartRequest.cs
@@ -0,0 +1,8 @@
+namespace HotChocolate.AspNetCore.Parsers;
+
+internal sealed class HttpMultipartRequest(string operations, IDictionary fileMap)
+{
+ public string Operations { get; set; } = operations;
+
+ public IDictionary FileMap { get; set; } = fileMap;
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpRequestParser.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IHttpRequestParser.cs
similarity index 98%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpRequestParser.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IHttpRequestParser.cs
index 8f94f65a168..d38549a4315 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpRequestParser.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IHttpRequestParser.cs
@@ -1,7 +1,7 @@
-using Microsoft.AspNetCore.Http;
using HotChocolate.Language;
+using Microsoft.AspNetCore.Http;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Parsers;
///
/// A helper to parse GraphQL HTTP requests.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IVariablePathSegment.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IVariablePathSegment.cs
similarity index 63%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IVariablePathSegment.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IVariablePathSegment.cs
index b2f7787f410..7b69952d517 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IVariablePathSegment.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IVariablePathSegment.cs
@@ -1,4 +1,4 @@
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Parsers;
internal interface IVariablePathSegment
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IndexPathSegment.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IndexPathSegment.cs
similarity index 84%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IndexPathSegment.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IndexPathSegment.cs
index a502e3eb549..9c7f75d1c89 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IndexPathSegment.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/IndexPathSegment.cs
@@ -1,4 +1,4 @@
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Parsers;
internal class IndexPathSegment : IVariablePathSegment
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/KeyPathSegment.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/KeyPathSegment.cs
similarity index 84%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/KeyPathSegment.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/KeyPathSegment.cs
index 03856a11659..d1acc50e606 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/KeyPathSegment.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/KeyPathSegment.cs
@@ -1,4 +1,4 @@
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Parsers;
internal class KeyPathSegment : IVariablePathSegment
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/UploadedFile.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/UploadedFile.cs
new file mode 100644
index 00000000000..745b74a68e5
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/UploadedFile.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Http;
+
+namespace HotChocolate.AspNetCore.Parsers;
+
+internal sealed class UploadedFile(IFormFile file) : IFile
+{
+ public string Name => file.FileName;
+
+ public long? Length => file.Length;
+
+ public string? ContentType => file.ContentType;
+
+ public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default)
+ => file.CopyToAsync(target, cancellationToken);
+
+ public Stream OpenReadStream()
+ => file.OpenReadStream();
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/VariablePath.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/VariablePath.cs
similarity index 81%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/VariablePath.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/VariablePath.cs
index 07142f8db06..3e340f883cb 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/VariablePath.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Parsers/VariablePath.cs
@@ -1,15 +1,11 @@
-using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;
+using HotChocolate.AspNetCore.Utilities;
+using static HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources;
-namespace HotChocolate.AspNetCore.Serialization;
+namespace HotChocolate.AspNetCore.Parsers;
-internal class VariablePath
+internal sealed class VariablePath(KeyPathSegment key)
{
- public VariablePath(KeyPathSegment key)
- {
- Key = key;
- }
-
- public KeyPathSegment Key { get; }
+ public KeyPathSegment Key { get; } = key;
public static VariablePath Parse(string s)
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/PersistedOperationMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/PersistedOperationMiddleware.cs
new file mode 100644
index 00000000000..239de43502d
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/PersistedOperationMiddleware.cs
@@ -0,0 +1,282 @@
+using System.Net;
+using HotChocolate.AspNetCore.Utilities;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+
+namespace HotChocolate.AspNetCore;
+
+internal static class PersistedOperationMiddleware
+{
+ internal static void MapPersistedOperationMiddleware(
+ this RouteGroupBuilder groupBuilder,
+ IServiceProvider services,
+ string schemaName,
+ bool requireOperationName)
+ {
+ var optionsHolder = new OptionsHolder();
+ var executorProxy = HttpRequestExecutorProxy.Create(services, schemaName);
+
+ groupBuilder.MapGet(
+ "/{operationId}",
+ async context =>
+ {
+ var options = optionsHolder.GetOptions(context);
+ var operationId = context.Request.RouteValues["operationId"] as string;
+
+ if (string.IsNullOrEmpty(operationId))
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ await context.Response.WriteAsync("Missing operationId", context.RequestAborted);
+ return;
+ }
+
+ await ExecuteGetRequestAsync(
+ context,
+ executorProxy,
+ options,
+ operationId,
+ operationName: null,
+ requireOperationName);
+ });
+
+ groupBuilder
+ .MapGet(
+ "/{operationId}/{operationName}",
+ async context =>
+ {
+ var options = optionsHolder.GetOptions(context);
+ var operationId = context.Request.RouteValues["operationId"] as string;
+ var operationName = context.Request.RouteValues["operationName"] as string;
+
+ if (string.IsNullOrEmpty(operationId))
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ await context.Response.WriteAsync("Missing operationId", context.RequestAborted);
+ return;
+ }
+
+ if (string.IsNullOrEmpty(operationName))
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ await context.Response.WriteAsync("Missing operationName", context.RequestAborted);
+ return;
+ }
+
+ await ExecuteGetRequestAsync(
+ context,
+ executorProxy,
+ options,
+ operationId,
+ operationName,
+ requireOperationName);
+ });
+
+ groupBuilder
+ .MapPost(
+ "/{operationId}",
+ async context =>
+ {
+ var operationId = context.Request.RouteValues["operationId"] as string;
+
+ if (string.IsNullOrEmpty(operationId))
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ await context.Response.WriteAsync("Missing operationId", context.RequestAborted);
+ return;
+ }
+
+ await ExecutePostRequestAsync(
+ context,
+ executorProxy,
+ operationId,
+ operationName: null,
+ requireOperationName);
+ });
+
+ groupBuilder
+ .MapPost(
+ "/{operationId}/{operationName}",
+ async context =>
+ {
+ var operationId = context.Request.RouteValues["operationId"] as string;
+ var operationName = context.Request.RouteValues["operationName"] as string;
+
+ if (string.IsNullOrEmpty(operationId))
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ await context.Response.WriteAsync("Missing operationId", context.RequestAborted);
+ return;
+ }
+
+ if (string.IsNullOrEmpty(operationName))
+ {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ await context.Response.WriteAsync("Missing operationName", context.RequestAborted);
+ return;
+ }
+
+ await ExecutePostRequestAsync(
+ context,
+ executorProxy,
+ operationId,
+ operationName,
+ requireOperationName);
+ });
+ }
+
+ private static async Task ExecuteGetRequestAsync(
+ HttpContext context,
+ HttpRequestExecutorProxy executorProxy,
+ GraphQLServerOptions options,
+ string operationId,
+ string? operationName,
+ bool requireOperationName)
+ {
+ HttpStatusCode? statusCode;
+ IExecutionResult? result;
+ var ct = context.RequestAborted;
+ var executorSession = await executorProxy.GetOrCreateSessionAsync(ct);
+
+ // first, we validate the accept-headers.
+ var validationResult = MiddlewareHelper.ValidateAcceptContentType(context, executorSession);
+ var acceptMediaTypes = validationResult.AcceptMediaTypes;
+
+ if (!validationResult.IsValid)
+ {
+ statusCode = validationResult.StatusCode.Value;
+ result = validationResult.Error;
+ goto HANDLE_RESULT;
+ }
+
+ // validate if the operation name is required.
+ if (requireOperationName && string.IsNullOrWhiteSpace(operationName))
+ {
+ statusCode = HttpStatusCode.BadRequest;
+ result = ErrorHelper.OperationNameRequired();
+ goto HANDLE_RESULT;
+ }
+
+ // next, we parse the GraphQL request.
+ var parserResult =
+ MiddlewareHelper.ParseVariablesAndExtensionsFromParams(
+ operationId,
+ operationName,
+ context,
+ executorSession);
+
+ if (!parserResult.IsValid)
+ {
+ statusCode = parserResult.StatusCode.Value;
+ result = parserResult.Error;
+ goto HANDLE_RESULT;
+ }
+
+ // before we can execute the request, we need to determine the request flags.
+ var request = parserResult.Request!;
+ var requestFlags =
+ MiddlewareHelper.DetermineHttpGetRequestFlags(
+ validationResult.RequestFlags,
+ options);
+
+ // next, we will execute the request.
+ var executionResult =
+ await MiddlewareHelper.ExecuteRequestAsync(
+ request,
+ requestFlags,
+ context,
+ executorSession);
+ statusCode = executionResult.StatusCode;
+ result = executionResult.Result;
+
+HANDLE_RESULT:
+ await MiddlewareHelper.WriteResultAsync(
+ result!,
+ acceptMediaTypes,
+ statusCode,
+ context,
+ executorSession);
+ }
+
+ private static async Task ExecutePostRequestAsync(
+ HttpContext context,
+ HttpRequestExecutorProxy executorProxy,
+ string operationId,
+ string? operationName,
+ bool requireOperationName)
+ {
+ HttpStatusCode? statusCode;
+ IExecutionResult? result;
+ var ct = context.RequestAborted;
+ var executorSession = await executorProxy.GetOrCreateSessionAsync(ct);
+
+ // first, we validate the accept-headers.
+ var validationResult = MiddlewareHelper.ValidateAcceptContentType(context, executorSession);
+
+ var acceptMediaTypes = validationResult.AcceptMediaTypes;
+
+ if (!validationResult.IsValid)
+ {
+ statusCode = validationResult.StatusCode.Value;
+ result = validationResult.Error;
+ goto HANDLE_RESULT;
+ }
+
+ // validate if the operation name is required.
+ if (requireOperationName && string.IsNullOrWhiteSpace(operationName))
+ {
+ statusCode = HttpStatusCode.BadRequest;
+ result = ErrorHelper.OperationNameRequired();
+ goto HANDLE_RESULT;
+ }
+
+ // next, we parse the GraphQL request.
+ var parserResult =
+ await MiddlewareHelper.ParseSingleRequestFromBodyAsync(
+ operationId,
+ operationName,
+ context,
+ executorSession);
+
+ if (!parserResult.IsValid)
+ {
+ statusCode = parserResult.StatusCode.Value;
+ result = parserResult.Error;
+ goto HANDLE_RESULT;
+ }
+
+ // after successfully parsing the request, we now will attempt to execute the request.
+ var executionResult =
+ await MiddlewareHelper.ExecuteRequestAsync(
+ parserResult.Request!,
+ validationResult.RequestFlags,
+ context,
+ executorSession);
+ statusCode = executionResult.StatusCode;
+ result = executionResult.Result;
+
+HANDLE_RESULT:
+ await MiddlewareHelper.WriteResultAsync(
+ result!,
+ acceptMediaTypes,
+ statusCode,
+ context,
+ executorSession);
+ }
+
+ private sealed class OptionsHolder
+ {
+ private GraphQLServerOptions? _options;
+
+ public GraphQLServerOptions GetOptions(HttpContext context)
+ {
+ if (_options is not null)
+ {
+ return _options;
+ }
+
+ _options = context.GetGraphQLServerOptions() ?? new GraphQLServerOptions();
+ return _options;
+ }
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Properties/AspNetCorePipelineResources.Designer.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Properties/AspNetCorePipelineResources.Designer.cs
new file mode 100644
index 00000000000..0cc7e5e7751
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Properties/AspNetCorePipelineResources.Designer.cs
@@ -0,0 +1,276 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace HotChocolate.AspNetCore.Properties {
+ using System;
+
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class AspNetCorePipelineResources {
+
+ private static System.Resources.ResourceManager resourceMan;
+
+ private static System.Globalization.CultureInfo resourceCulture;
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal AspNetCorePipelineResources() {
+ }
+
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.Equals(null, resourceMan)) {
+ System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources", typeof(AspNetCorePipelineResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ internal static string ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty {
+ get {
+ return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing {
+ get {
+ return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded {
+ get {
+ return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported {
+ get {
+ return ResourceManager.GetString("ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_Invalid_Form {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_Invalid_Form", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_No_Operations_Specified {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_No_Operations_Specified", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_Fields_Misordered {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_Fields_Misordered", resourceCulture);
+ }
+ }
+
+ internal static string HttpMultipartMiddleware_InsertFilesIntoRequest_VariablesImmutable {
+ get {
+ return ResourceManager.GetString("HttpMultipartMiddleware_InsertFilesIntoRequest_VariablesImmutable", resourceCulture);
+ }
+ }
+
+ internal static string VariablePath_Parse_FirstSegmentMustBeKey {
+ get {
+ return ResourceManager.GetString("VariablePath_Parse_FirstSegmentMustBeKey", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_NoObjectPath {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_NoObjectPath", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_FileMissing {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_FileMissing", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_VariableNotFound {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_VariableNotFound", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_VariableStructureInvalid {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_VariableStructureInvalid", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_InvalidPath {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_InvalidPath", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_PathMustStartWithVariable {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_PathMustStartWithVariable", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_InvalidMapJson {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_InvalidMapJson", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_HttpMultipartMiddleware_MapNotSpecified {
+ get {
+ return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_MapNotSpecified", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_InvalidRequest {
+ get {
+ return ResourceManager.GetString("ErrorHelper_InvalidRequest", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_RequestHasNoElements {
+ get {
+ return ResourceManager.GetString("ErrorHelper_RequestHasNoElements", resourceCulture);
+ }
+ }
+
+ internal static string WebSocketSession_SessionEnded {
+ get {
+ return ResourceManager.GetString("WebSocketSession_SessionEnded", resourceCulture);
+ }
+ }
+
+ internal static string DataStartMessageHandler_Not_A_SubscriptionResult {
+ get {
+ return ResourceManager.GetString("DataStartMessageHandler_Not_A_SubscriptionResult", resourceCulture);
+ }
+ }
+
+ internal static string TerminateConnectionMessageHandler_Message {
+ get {
+ return ResourceManager.GetString("TerminateConnectionMessageHandler_Message", resourceCulture);
+ }
+ }
+
+ internal static string Apollo_OnReceive_MessageMustBeJson {
+ get {
+ return ResourceManager.GetString("Apollo_OnReceive_MessageMustBeJson", resourceCulture);
+ }
+ }
+
+ internal static string Apollo_OnReceive_TypePropMissing {
+ get {
+ return ResourceManager.GetString("Apollo_OnReceive_TypePropMissing", resourceCulture);
+ }
+ }
+
+ internal static string Apollo_OnReceive_ToManyInitializations {
+ get {
+ return ResourceManager.GetString("Apollo_OnReceive_ToManyInitializations", resourceCulture);
+ }
+ }
+
+ internal static string Apollo_OnReceive_InvalidSubscribeMessage {
+ get {
+ return ResourceManager.GetString("Apollo_OnReceive_InvalidSubscribeMessage", resourceCulture);
+ }
+ }
+
+ internal static string Apollo_OnReceive_SubscriptionIdNotUnique {
+ get {
+ return ResourceManager.GetString("Apollo_OnReceive_SubscriptionIdNotUnique", resourceCulture);
+ }
+ }
+
+ internal static string Apollo_OnReceive_InvalidMessageType {
+ get {
+ return ResourceManager.GetString("Apollo_OnReceive_InvalidMessageType", resourceCulture);
+ }
+ }
+
+ internal static string ConnectionStatus_Reject_Message_cannot_be_null_or_empty_ {
+ get {
+ return ResourceManager.GetString("ConnectionStatus_Reject_Message_cannot_be_null_or_empty_", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_NoSupportedAcceptMediaType {
+ get {
+ return ResourceManager.GetString("ErrorHelper_NoSupportedAcceptMediaType", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_Formatter_ResultKindNotSupported {
+ get {
+ return ResourceManager.GetString("ThrowHelper_Formatter_ResultKindNotSupported", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_Formatter_ResponseContentTypeNotSupported {
+ get {
+ return ResourceManager.GetString("ThrowHelper_Formatter_ResponseContentTypeNotSupported", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_Formatter_InvalidAcceptMediaType {
+ get {
+ return ResourceManager.GetString("ThrowHelper_Formatter_InvalidAcceptMediaType", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_InvalidAcceptMediaType {
+ get {
+ return ResourceManager.GetString("ErrorHelper_InvalidAcceptMediaType", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_TypeNotFound {
+ get {
+ return ResourceManager.GetString("ErrorHelper_TypeNotFound", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_InvalidTypeName {
+ get {
+ return ResourceManager.GetString("ErrorHelper_InvalidTypeName", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_TypeNameIsEmpty {
+ get {
+ return ResourceManager.GetString("ErrorHelper_TypeNameIsEmpty", resourceCulture);
+ }
+ }
+
+ internal static string ErrorHelper_MultiPartRequestPreflightRequired {
+ get {
+ return ResourceManager.GetString("ErrorHelper_MultiPartRequestPreflightRequired", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Properties/AspNetCorePipelineResources.resx b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Properties/AspNetCorePipelineResources.resx
new file mode 100644
index 00000000000..50718993e9c
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Properties/AspNetCorePipelineResources.resx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The GraphQL request is empty.
+
+
+ Either the parameter query or the parameter id has to be set.
+
+
+ Max GraphQL request size reached.
+
+
+ The response type is not supported.
+
+
+ The multipart form could not be read.
+
+
+ No 'operations' specified.
+
+
+ Misordered multipart fields; 'map' should follow 'operations'.
+
+
+ The variables are expected to mutable at this point.
+
+
+ The first path segment must be a key.
+
+
+ No object paths specified for key '{0}' in 'map'.
+
+
+ File of key '{0}' is missing.
+
+
+ The variable path '{0}' is invalid.
+
+
+ The variable structure is invalid.
+
+
+ Invalid variable path `{0}` in `map`.
+
+
+ The variable path must start with `variables`.
+
+
+ Invalid JSON in the `map` multipart field; Expected type of Dictionary<string, string[]>.
+
+
+ No `map` specified.
+
+
+ Invalid GraphQL Request.
+
+
+ The GraphQL batch request has no elements.
+
+
+ Session ended.
+
+
+ The specified result object is not a valid subscription result.
+
+
+ Connection terminated by user.
+
+
+ A message must be a json object.
+
+
+ The type property on the message is mandatory.
+
+
+ Too many initialization requests.
+
+
+ Invalid subscribe message structure.
+
+
+ The subscription id is not unique.
+
+
+ Invalid message type.
+
+
+ Message cannot be null or empty.
+
+
+ None of the `Accept` header values is supported.
+
+
+ The execution result kind is not supported.
+
+
+ The specified response content-type `{0}` is not supported.
+
+
+ Invalid accept media types specified.
+
+
+ Unable to parse the accept header value `{0}`.
+
+
+ The type `{0}` does not exist.
+
+
+ The type name is invalid.
+
+
+ The specified types argument is empty.
+
+
+ Multi-part requests must include a GraphQL preflight header.
+
+
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/RequestContentType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/RequestContentType.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/RequestContentType.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/RequestContentType.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ResponseContentType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ResponseContentType.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/ResponseContentType.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/ResponseContentType.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ConnectionCloseReason.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ConnectionCloseReason.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ConnectionCloseReason.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ConnectionCloseReason.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/IOperationManager.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/IOperationManager.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/IOperationManager.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/IOperationManager.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/IOperationSession.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/IOperationSession.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/IOperationSession.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/IOperationSession.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ISocketConnection.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ISocketConnection.cs
similarity index 87%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ISocketConnection.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ISocketConnection.cs
index d4ffe15d38c..f8a50c7fa39 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ISocketConnection.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ISocketConnection.cs
@@ -1,14 +1,15 @@
using HotChocolate.AspNetCore.Subscriptions.Protocols;
+using HotChocolate.Features;
using HotChocolate.Transport.Sockets;
using Microsoft.AspNetCore.Http;
namespace HotChocolate.AspNetCore.Subscriptions;
///
-/// The socket connection represent an accepted connection with a socket
+/// The socket connection represents an accepted connection with a socket
/// where the protocol is already negotiated.
///
-public interface ISocketConnection : ISocket, IHasContextData, IDisposable
+public interface ISocketConnection : ISocket, IFeatureProvider, IDisposable
{
///
/// Gets access to the HTTP Context.
@@ -25,6 +26,11 @@ public interface ISocketConnection : ISocket, IHasContextData, IDisposable
///
CancellationToken RequestAborted { get; }
+ ///
+ /// Specifies if the connection is connected to a client.
+ ///
+ bool IsConnected { get; set; }
+
///
/// Tries to accept the connection and returns the accepted protocol handler.
///
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ISocketSession.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ISocketSession.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ISocketSession.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/ISocketSession.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/OperationManager.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/OperationManager.cs
similarity index 69%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/OperationManager.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/OperationManager.cs
index 20103381636..3d42829e22d 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/OperationManager.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/OperationManager.cs
@@ -1,6 +1,5 @@
using System.Collections;
using HotChocolate.Language;
-using Microsoft.Extensions.DependencyInjection;
namespace HotChocolate.AspNetCore.Subscriptions;
@@ -16,39 +15,40 @@ public sealed class OperationManager : IOperationManager
private readonly CancellationTokenSource _cts;
private readonly CancellationToken _cancellationToken;
private readonly ISocketSession _socketSession;
- private readonly ISocketSessionInterceptor _interceptor;
- private readonly IErrorHandler _errorHandler;
- private readonly IRequestExecutor _executor;
+ private readonly ExecutorSession _executorSession;
private readonly Func _createSession;
private bool _disposed;
- public OperationManager(
- ISocketSession socketSession,
- ISocketSessionInterceptor interceptor,
- IRequestExecutor executor)
+ public OperationManager(ISocketSession socketSession, ExecutorSession executorSession)
{
- _socketSession = socketSession ?? throw new ArgumentNullException(nameof(socketSession));
- _interceptor = interceptor ?? throw new ArgumentNullException(nameof(interceptor));
- _executor = executor ?? throw new ArgumentNullException(nameof(executor));
+ ArgumentNullException.ThrowIfNull(socketSession);
+ ArgumentNullException.ThrowIfNull(executorSession);
+
+ _socketSession = socketSession;
+ _executorSession = executorSession;
+
_createSession = CreateSession;
- _errorHandler = executor.Schema.Services.GetRequiredService();
_cts = new CancellationTokenSource();
_cancellationToken = _cts.Token;
+ _createSession = CreateSession;
}
internal OperationManager(
ISocketSession socketSession,
- ISocketSessionInterceptor interceptor,
- IRequestExecutor executor,
+ ExecutorSession executorSession,
Func createSession)
{
- _socketSession = socketSession ?? throw new ArgumentNullException(nameof(socketSession));
- _interceptor = interceptor ?? throw new ArgumentNullException(nameof(interceptor));
- _executor = executor ?? throw new ArgumentNullException(nameof(executor));
- _createSession = createSession ?? throw new ArgumentNullException(nameof(createSession));
- _errorHandler = executor.Schema.Services.GetRequiredService();
+ ArgumentNullException.ThrowIfNull(socketSession);
+ ArgumentNullException.ThrowIfNull(executorSession);
+ ArgumentNullException.ThrowIfNull(createSession);
+
+ _socketSession = socketSession;
+ _executorSession = executorSession;
+
+ _createSession = CreateSession;
_cts = new CancellationTokenSource();
_cancellationToken = _cts.Token;
+ _createSession = createSession;
}
///
@@ -94,9 +94,8 @@ public bool Complete(string sessionId)
try
{
- if (_subs.TryGetValue(sessionId, out var session))
+ if (_subs.Remove(sessionId, out var session))
{
- _subs.Remove(sessionId);
session.Dispose();
return true;
}
@@ -110,7 +109,7 @@ public bool Complete(string sessionId)
}
private OperationSession CreateSession(string sessionId)
- => new(_socketSession, _interceptor, _errorHandler, _executor, sessionId);
+ => new(_socketSession, _executorSession, sessionId);
///
public void Dispose()
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/OperationSession.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/OperationSession.cs
similarity index 89%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/OperationSession.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/OperationSession.cs
index f60e2fb2791..56b54fe7441 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/OperationSession.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/OperationSession.cs
@@ -9,24 +9,20 @@ internal sealed class OperationSession : IOperationSession
private readonly CancellationToken _ct;
private readonly ISocketSession _session;
private readonly ISocketSessionInterceptor _interceptor;
- private readonly IErrorHandler _errorHandler;
- private readonly IRequestExecutor _executor;
+ private readonly ExecutorSession _executorSession;
private bool _disposed;
public event EventHandler? Completed;
public OperationSession(
ISocketSession session,
- ISocketSessionInterceptor interceptor,
- IErrorHandler errorHandler,
- IRequestExecutor executor,
+ ExecutorSession executorSession,
string id)
{
- _ct = _cts.Token;
_session = session;
- _interceptor = interceptor;
- _errorHandler = errorHandler;
- _executor = executor;
+ _executorSession = executorSession;
+ _interceptor = executorSession.SocketSessionInterceptor;
+ _ct = _cts.Token;
Id = id;
}
@@ -47,7 +43,7 @@ private async Task SendResultsAsync(GraphQLRequest request, CancellationToken ca
{
var requestBuilder = CreateRequestBuilder(request);
await _interceptor.OnRequestAsync(_session, Id, requestBuilder, ct);
- await using var result = await _executor.ExecuteAsync(requestBuilder.Build(), ct);
+ await using var result = await _executorSession.ExecuteAsync(requestBuilder.Build(), ct);
switch (result)
{
@@ -71,7 +67,7 @@ await _session.Protocol.SendErrorMessageAsync(
{
try
{
- // use original cancellation token here to keep the websocket open for other streams.
+ // use the original cancellation token here to keep the websocket open for other streams.
await SendResultMessageAsync(item, cancellationToken);
}
finally
@@ -82,8 +78,8 @@ await _session.Protocol.SendErrorMessageAsync(
break;
}
- // the operation is completed and we will try to send a complete message.
- // we mark completeTry true so that in case of an error we do not try to send this
+ // The operation is completed, and we will try to send a complete message.
+ // We mark 'completeTry' true so that in case of an error, we do not try to send this
// message again.
completeTry = true;
@@ -175,7 +171,7 @@ private async Task TrySendErrorMessageAsync(Exception exception, CancellationTok
if (!ct.IsCancellationRequested)
{
var error = ErrorBuilder.FromException(exception).Build();
- error = _errorHandler.Handle(error);
+ error = _executorSession.Handle(error);
var errors =
error is AggregateError aggregateError
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/PingPongJob.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/PingPongJob.cs
similarity index 55%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/PingPongJob.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/PingPongJob.cs
index a285cd3a4ff..07434a8000a 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/PingPongJob.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/PingPongJob.cs
@@ -2,40 +2,31 @@
namespace HotChocolate.AspNetCore.Subscriptions;
-internal sealed class PingPongJob
+internal sealed class PingPongJob(ISocketSession session, GraphQLSocketOptions options)
{
- private readonly ISocketSession _session;
- private readonly GraphQLSocketOptions _options;
-
- public PingPongJob(ISocketSession session, GraphQLSocketOptions options)
- {
- _session = session;
- _options = options;
- }
-
public async Task RunAsync(CancellationToken cancellationToken)
{
- var connection = _session.Connection;
- var protocolHandler = _session.Protocol;
+ var connection = session.Connection;
+ var protocolHandler = session.Protocol;
try
{
- // first we will wait for a connection to be established
- await Task.Delay(_options.ConnectionInitializationTimeout, cancellationToken);
+ // first, we will wait for a connection to be established
+ await Task.Delay(options.ConnectionInitializationTimeout, cancellationToken);
- // if after the timeout no connection initialization was send by the client we will
+ // if after the timeout no connection initialization was sent by the client, we will
// close the connection.
- if (!connection.ContextData.ContainsKey(ConnectionContextKeys.Connected))
+ if (!connection.IsConnected)
{
- await _session.Protocol.OnConnectionInitTimeoutAsync(_session, cancellationToken);
+ await session.Protocol.OnConnectionInitTimeoutAsync(session, cancellationToken);
return;
}
// if a keep alive interval is configured we will start sending connection
// keep alive messages to the client.
- if (_options.KeepAliveInterval is not null)
+ if (options.KeepAliveInterval is not null)
{
- var interval = _options.KeepAliveInterval.Value;
+ var interval = options.KeepAliveInterval.Value;
while (!connection.IsClosed && !cancellationToken.IsCancellationRequested)
{
@@ -43,7 +34,7 @@ public async Task RunAsync(CancellationToken cancellationToken)
if (!connection.IsClosed)
{
- await protocolHandler.SendKeepAliveMessageAsync(_session, cancellationToken);
+ await protocolHandler.SendKeepAliveMessageAsync(session, cancellationToken);
}
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs
similarity index 91%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs
index 04e8ad4952b..f8541257cab 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs
@@ -1,11 +1,10 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
-using HotChocolate.AspNetCore.Serialization;
+using HotChocolate.AspNetCore.Formatters;
using HotChocolate.Buffers;
using HotChocolate.Language;
-using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;
-using static HotChocolate.AspNetCore.Subscriptions.ConnectionContextKeys;
+using static HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources;
using static HotChocolate.AspNetCore.Subscriptions.Protocols.Apollo.MessageProperties;
using static HotChocolate.AspNetCore.Subscriptions.Protocols.MessageUtilities;
using static HotChocolate.Language.Utf8GraphQLRequestParser;
@@ -52,7 +51,7 @@ private async ValueTask OnReceiveInternalAsync(
CancellationToken cancellationToken)
{
var connection = session.Connection;
- var connected = connection.ContextData.ContainsKey(Connected);
+ var connected = connection.IsConnected;
if (connected && message.IsSingleSegment &&
message.First.Equals(Utf8MessageBodies.KeepAlive))
@@ -108,7 +107,7 @@ await _interceptor.OnConnectAsync(
if (connectionStatus.Accepted)
{
- connection.ContextData.Add(Connected, true);
+ connection.IsConnected = true;
await SendConnectionAcceptMessage(
session,
connectionStatus.Extensions,
@@ -149,7 +148,6 @@ await connection.CloseAsync(
Apollo_OnReceive_SubscriptionIdNotUnique,
ConnectionCloseReason.InternalServerError,
cancellationToken);
- return;
}
}
catch (GraphQLRequestException ex)
@@ -250,7 +248,7 @@ public async ValueTask SendResultMessageAsync(
_formatter.Format(result, jsonWriter);
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(cancellationToken);
- await session.Connection.SendAsync(arrayWriter.GetWrittenMemory(), cancellationToken);
+ await session.Connection.SendAsync(arrayWriter.WrittenMemory, cancellationToken);
}
public async ValueTask SendErrorMessageAsync(
@@ -268,7 +266,7 @@ public async ValueTask SendErrorMessageAsync(
_formatter.Format(errors[0], jsonWriter);
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(cancellationToken);
- await session.Connection.SendAsync(arrayWriter.GetWrittenMemory(), cancellationToken);
+ await session.Connection.SendAsync(arrayWriter.WrittenMemory, cancellationToken);
}
public async ValueTask SendCompleteMessageAsync(
@@ -277,21 +275,21 @@ public async ValueTask SendCompleteMessageAsync(
CancellationToken cancellationToken)
{
using var writer = new PooledArrayWriter();
- SerializeMessage(writer, Utf8Messages.Complete, id: operationSessionId);
- await session.Connection.SendAsync(writer.GetWrittenMemory(), cancellationToken);
+ SerializeMessage(writer, _formatter, Utf8Messages.Complete, id: operationSessionId);
+ await session.Connection.SendAsync(writer.WrittenMemory, cancellationToken);
}
- private static async ValueTask SendConnectionAcceptMessage(
+ private async ValueTask SendConnectionAcceptMessage(
ISocketSession session,
IReadOnlyDictionary? payload,
CancellationToken cancellationToken)
{
using var writer = new PooledArrayWriter();
- SerializeMessage(writer, Utf8Messages.ConnectionAccept, payload);
- await session.Connection.SendAsync(writer.GetWrittenMemory(), cancellationToken);
+ SerializeMessage(writer, _formatter, Utf8Messages.ConnectionAccept, payload);
+ await session.Connection.SendAsync(writer.WrittenMemory, cancellationToken);
}
- private static async ValueTask SendConnectionRejectMessage(
+ private async ValueTask SendConnectionRejectMessage(
ISocketSession session,
string message,
IReadOnlyDictionary? extensions,
@@ -299,17 +297,27 @@ private static async ValueTask SendConnectionRejectMessage(
{
using var arrayWriter = new PooledArrayWriter();
await using var jsonWriter = new Utf8JsonWriter(arrayWriter, WriterOptions);
+
jsonWriter.WriteStartObject();
jsonWriter.WriteString(MessageProperties.Type, Utf8Messages.ConnectionError);
jsonWriter.WritePropertyName(Payload);
jsonWriter.WriteStartObject();
jsonWriter.WriteString(Message, message);
jsonWriter.WritePropertyName(MessageProperties.Extensions);
- JsonSerializer.Serialize(jsonWriter, extensions);
+
+ if (extensions is null)
+ {
+ jsonWriter.WriteNullValue();
+ }
+ else
+ {
+ _formatter.Format(extensions, jsonWriter);
+ }
+
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(cancellationToken);
- await session.Connection.SendAsync(arrayWriter.GetWrittenMemory(), cancellationToken);
+ await session.Connection.SendAsync(arrayWriter.WrittenMemory, cancellationToken);
}
public ValueTask OnConnectionInitTimeoutAsync(
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/CloseReasons.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/CloseReasons.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/CloseReasons.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/CloseReasons.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/DataStartMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/DataStartMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/DataStartMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/DataStartMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/InitializeConnectionMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/InitializeConnectionMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/InitializeConnectionMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/InitializeConnectionMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/MessageProperties.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/MessageProperties.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/MessageProperties.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/MessageProperties.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Messages.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Messages.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Messages.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Messages.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8MessageBodies.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8MessageBodies.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8MessageBodies.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8MessageBodies.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8MessageProperties.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8MessageProperties.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8MessageProperties.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8MessageProperties.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8Messages.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8Messages.cs
new file mode 100644
index 00000000000..898671b6b35
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/Utf8Messages.cs
@@ -0,0 +1,22 @@
+namespace HotChocolate.AspNetCore.Subscriptions.Protocols.Apollo;
+
+internal static class Utf8Messages
+{
+ public static ReadOnlySpan ConnectionInitialize => "connection_init"u8;
+
+ public static ReadOnlySpan ConnectionAccept => "connection_ack"u8;
+
+ public static ReadOnlySpan ConnectionError => "connection_error"u8;
+
+ public static ReadOnlySpan ConnectionTerminate => "connection_terminate"u8;
+
+ public static ReadOnlySpan Start => "start"u8;
+
+ public static ReadOnlySpan Stop => "stop"u8;
+
+ public static ReadOnlySpan Data => "data"u8;
+
+ public static ReadOnlySpan Error => "error"u8;
+
+ public static ReadOnlySpan Complete => "complete"u8;
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/ConnectionStatus.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/ConnectionStatus.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/ConnectionStatus.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/ConnectionStatus.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/CloseReasons.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/CloseReasons.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/CloseReasons.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/CloseReasons.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionExtensions.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionExtensions.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionExtensions.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionInitMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionInitMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionInitMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/ConnectionInitMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs
similarity index 89%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs
index a35ef558788..de6ba6170e4 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs
@@ -1,11 +1,12 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
+#if NET9_0_OR_GREATER
using System.Runtime.InteropServices;
+#endif
using System.Text.Json;
-using HotChocolate.AspNetCore.Serialization;
+using HotChocolate.AspNetCore.Formatters;
using HotChocolate.Buffers;
using HotChocolate.Language;
-using static HotChocolate.AspNetCore.Subscriptions.ConnectionContextKeys;
using static HotChocolate.AspNetCore.Subscriptions.Protocols.GraphQLOverWebSocket.MessageProperties;
using static HotChocolate.AspNetCore.Subscriptions.Protocols.MessageUtilities;
using static HotChocolate.Language.Utf8GraphQLRequestParser;
@@ -50,7 +51,7 @@ private async ValueTask OnReceiveInternalAsync(
{
var connection = session.Connection;
- var connected = connection.ContextData.ContainsKey(Connected);
+ var connected = connection.IsConnected;
using var document = JsonDocument.Parse(message);
var root = document.RootElement;
JsonElement idProp;
@@ -114,7 +115,7 @@ await _interceptor.OnConnectAsync(
if (connectionStatus.Accepted)
{
- connection.ContextData.Add(Connected, true);
+ connection.IsConnected = true;
await SendConnectionAcceptMessage(
session,
connectionStatus.Extensions,
@@ -149,7 +150,6 @@ await SendConnectionAcceptMessage(
if (!session.Operations.Enqueue(subscribeMessage.Id, subscribeMessage.Payload))
{
await connection.CloseSubscriptionIdNotUniqueAsync(cancellationToken);
- return;
}
}
catch (GraphQLRequestException ex)
@@ -223,7 +223,7 @@ public async ValueTask SendResultMessageAsync(
_formatter.Format(result, jsonWriter);
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(cancellationToken);
- await session.Connection.SendAsync(arrayWriter.GetWrittenMemory(), cancellationToken);
+ await session.Connection.SendAsync(arrayWriter.WrittenMemory, cancellationToken);
}
public async ValueTask SendErrorMessageAsync(
@@ -241,7 +241,7 @@ public async ValueTask SendErrorMessageAsync(
_formatter.Format(errors, jsonWriter);
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(cancellationToken);
- await session.Connection.SendAsync(arrayWriter.GetWrittenMemory(), cancellationToken);
+ await session.Connection.SendAsync(arrayWriter.WrittenMemory, cancellationToken);
}
public async ValueTask SendCompleteMessageAsync(
@@ -250,8 +250,8 @@ public async ValueTask SendCompleteMessageAsync(
CancellationToken cancellationToken)
{
using var writer = new PooledArrayWriter();
- SerializeMessage(writer, Utf8Messages.Complete, id: operationSessionId);
- await session.Connection.SendAsync(writer.GetWrittenMemory(), cancellationToken);
+ SerializeMessage(writer, _formatter, Utf8Messages.Complete, id: operationSessionId);
+ await session.Connection.SendAsync(writer.WrittenMemory, cancellationToken);
}
public async ValueTask SendPingMessageAsync(
@@ -266,12 +266,12 @@ public async ValueTask SendPingMessageAsync(
else
{
using var writer = new PooledArrayWriter();
- SerializeMessage(writer, Utf8Messages.Ping, payload);
- await session.Connection.SendAsync(writer.GetWrittenMemory(), cancellationToken);
+ SerializeMessage(writer, _formatter, Utf8Messages.Ping, payload);
+ await session.Connection.SendAsync(writer.WrittenMemory, cancellationToken);
}
}
- private static async ValueTask SendPongMessageAsync(
+ private async ValueTask SendPongMessageAsync(
ISocketSession session,
IReadOnlyDictionary? payload,
CancellationToken cancellationToken)
@@ -283,19 +283,19 @@ private static async ValueTask SendPongMessageAsync(
else
{
using var writer = new PooledArrayWriter();
- SerializeMessage(writer, Utf8Messages.Pong, payload);
- await session.Connection.SendAsync(writer.GetWrittenMemory(), cancellationToken);
+ SerializeMessage(writer, _formatter, Utf8Messages.Pong, payload);
+ await session.Connection.SendAsync(writer.WrittenMemory, cancellationToken);
}
}
- private static async ValueTask SendConnectionAcceptMessage(
+ private async ValueTask SendConnectionAcceptMessage(
ISocketSession session,
IReadOnlyDictionary? payload,
CancellationToken cancellationToken)
{
using var writer = new PooledArrayWriter();
- SerializeMessage(writer, Utf8Messages.ConnectionAccept, payload);
- await session.Connection.SendAsync(writer.GetWrittenMemory(), cancellationToken);
+ SerializeMessage(writer, _formatter, Utf8Messages.ConnectionAccept, payload);
+ await session.Connection.SendAsync(writer.WrittenMemory, cancellationToken);
}
public ValueTask OnConnectionInitTimeoutAsync(
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/IGraphQLOverWebSocketProtocolHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/IGraphQLOverWebSocketProtocolHandler.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/IGraphQLOverWebSocketProtocolHandler.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/IGraphQLOverWebSocketProtocolHandler.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/MessageProperties.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/MessageProperties.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/MessageProperties.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/MessageProperties.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Messages.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Messages.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Messages.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Messages.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/PingMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/PingMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/PingMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/PingMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/PongMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/PongMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/PongMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/PongMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/SubscribeMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/SubscribeMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/SubscribeMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/SubscribeMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageBodies.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageBodies.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageBodies.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageBodies.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageProperties.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageProperties.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageProperties.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8MessageProperties.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8Messages.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8Messages.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8Messages.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/Utf8Messages.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/IOperationMessagePayload.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/IOperationMessagePayload.cs
new file mode 100644
index 00000000000..73ba430aef5
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/IOperationMessagePayload.cs
@@ -0,0 +1,14 @@
+using System.Text.Json;
+
+namespace HotChocolate.AspNetCore.Subscriptions.Protocols;
+
+///
+/// The operation message with a custom payload.
+///
+public interface IOperationMessagePayload
+{
+ ///
+ /// Gets the JSON payload.
+ ///
+ JsonElement? Payload { get; }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/IProtocolHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/IProtocolHandler.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/IProtocolHandler.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/IProtocolHandler.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/JsonOperationMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/JsonOperationMessage.cs
similarity index 67%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/JsonOperationMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/JsonOperationMessage.cs
index e10f73c2176..0c64d4416f1 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/JsonOperationMessage.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/JsonOperationMessage.cs
@@ -1,5 +1,4 @@
using System.Text.Json;
-using static HotChocolate.AspNetCore.Subscriptions.Protocols.MessageUtilities;
namespace HotChocolate.AspNetCore.Subscriptions.Protocols;
@@ -14,8 +13,4 @@ protected JsonOperationMessage(string typeName, JsonElement? payload = null)
: base(typeName, payload)
{
}
-
- ///
- public T? As() where T : class
- => Payload?.Deserialize(SerializerOptions);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/MessageUtilities.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/MessageUtilities.cs
similarity index 91%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/MessageUtilities.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/MessageUtilities.cs
index 3dafbdd4c61..7b2c2b36c3d 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/MessageUtilities.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/MessageUtilities.cs
@@ -1,4 +1,5 @@
using System.Text.Json;
+using HotChocolate.AspNetCore.Formatters;
using HotChocolate.AspNetCore.Subscriptions.Protocols.GraphQLOverWebSocket;
using HotChocolate.Buffers;
@@ -14,6 +15,7 @@ internal static class MessageUtilities
public static void SerializeMessage(
PooledArrayWriter pooledArrayWriter,
+ IWebSocketPayloadFormatter formatter,
ReadOnlySpan type,
IReadOnlyDictionary? payload = null,
string? id = null)
@@ -31,7 +33,7 @@ public static void SerializeMessage(
if (payload is not null)
{
jsonWriter.WritePropertyName("payload");
- JsonSerializer.Serialize(jsonWriter, payload);
+ formatter.Format(payload, jsonWriter);
}
jsonWriter.WriteEndObject();
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/OperationMessage.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/OperationMessage.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/OperationMessage.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/OperationMessage.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/OperationMessage~1.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/OperationMessage~1.cs
similarity index 100%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/OperationMessage~1.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/OperationMessage~1.cs
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/WebSocketConnection.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/WebSocketConnection.cs
similarity index 93%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/WebSocketConnection.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/WebSocketConnection.cs
index 9c80f070d68..fc4252ffe9e 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/WebSocketConnection.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/WebSocketConnection.cs
@@ -1,6 +1,7 @@
using System.Buffers;
using System.Net.WebSockets;
using HotChocolate.AspNetCore.Subscriptions.Protocols;
+using HotChocolate.Features;
using HotChocolate.Transport.Sockets;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@@ -17,10 +18,12 @@ internal sealed class WebSocketConnection : ISocketConnection
private WebSocket? _webSocket;
private bool _disposed;
- public WebSocketConnection(HttpContext httpContext)
+ public WebSocketConnection(HttpContext httpContext, IRequestExecutor executor)
{
- HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
- var executor = (IRequestExecutor)httpContext.Items[WellKnownContextData.RequestExecutor]!;
+ ArgumentNullException.ThrowIfNull(httpContext);
+ ArgumentNullException.ThrowIfNull(executor);
+
+ HttpContext = httpContext;
_protocolHandlers = executor.Schema.Services.GetServices().ToArray();
}
@@ -35,7 +38,9 @@ public CancellationToken ApplicationStopping
public CancellationToken RequestAborted => HttpContext.RequestAborted;
- public IDictionary ContextData { get; } = new Dictionary();
+ public bool IsConnected { get; set; }
+
+ public IFeatureCollection Features { get; } = new FeatureCollection();
public async Task TryAcceptConnection()
{
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/WebSocketSession.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/WebSocketSession.cs
similarity index 80%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/WebSocketSession.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/WebSocketSession.cs
index d1d4ad632a2..51f7de01967 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/WebSocketSession.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/WebSocketSession.cs
@@ -1,8 +1,8 @@
using System.Buffers;
-using Microsoft.AspNetCore.Http;
using HotChocolate.AspNetCore.Subscriptions.Protocols;
using HotChocolate.Transport.Sockets;
-using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;
+using Microsoft.AspNetCore.Http;
+using static HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources;
using static HotChocolate.AspNetCore.Subscriptions.ConnectionCloseReason;
namespace HotChocolate.AspNetCore.Subscriptions;
@@ -15,12 +15,11 @@ internal sealed class WebSocketSession : ISocketSession
private WebSocketSession(
ISocketConnection connection,
IProtocolHandler protocol,
- ISocketSessionInterceptor interceptor,
- IRequestExecutor requestExecutor)
+ ExecutorSession executorSession)
{
Connection = connection;
Protocol = protocol;
- Operations = new OperationManager(this, interceptor, requestExecutor);
+ Operations = new OperationManager(this, executorSession);
}
public ISocketConnection Connection { get; }
@@ -41,26 +40,26 @@ public void Dispose()
public static async Task AcceptAsync(
HttpContext context,
- IRequestExecutor executor,
- ISocketSessionInterceptor interceptor)
+ ExecutorSession executorSession)
{
- using var connection = new WebSocketConnection(context);
+ using var connection = new WebSocketConnection(context, executorSession);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
context.RequestAborted,
connection.ApplicationStopping);
var ct = cts.Token;
var protocol = await connection.TryAcceptConnection();
+ var interceptor = executorSession.SocketSessionInterceptor;
if (protocol is not null)
{
- using var session = new WebSocketSession(connection, protocol, interceptor, executor);
+ using var session = new WebSocketSession(connection, protocol, executorSession);
var options = context.GetGraphQLSocketOptions() ?? s_defaultOptions;
try
{
var pingPong = new PingPongJob(session, options);
var pipeline = new MessagePipeline(connection, new ProtocolMessageHandler(session));
- pipeline.Completed += (_, _) => cts.Cancel();
+ pipeline.OnCompleted(static cts => cts.Cancel(), cts);
await Task.WhenAll(pingPong.RunAsync(ct), pipeline.RunAsync(ct));
}
catch (OperationCanceledException)
@@ -73,7 +72,8 @@ public static async Task AcceptAsync(
{
try
{
- await interceptor.OnCloseAsync(session, connection.HttpContext.RequestAborted);
+ await executorSession.SocketSessionInterceptor.OnCloseAsync(session,
+ connection.HttpContext.RequestAborted);
if (!connection.IsClosed)
{
@@ -86,7 +86,7 @@ await connection.CloseAsync(
}
catch
{
- // original exception must not be lost if new exception occurs
+ // the original exception must not be lost if new exception occurs
// during closing session
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/ErrorHelper.cs
similarity index 96%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/ErrorHelper.cs
index 73029fe8055..2f250d25e4e 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/ErrorHelper.cs
@@ -1,7 +1,7 @@
using HotChocolate.Collections.Immutable;
-using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;
+using static HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources;
-namespace HotChocolate.AspNetCore;
+namespace HotChocolate.AspNetCore.Utilities;
///
/// An internal helper class that centralizes server errors.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HeaderUtilities.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/HeaderUtilities.cs
similarity index 99%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/HeaderUtilities.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/HeaderUtilities.cs
index ae26dc99917..b49666beb19 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HeaderUtilities.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/HeaderUtilities.cs
@@ -6,7 +6,7 @@
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace HotChocolate.AspNetCore;
+namespace HotChocolate.AspNetCore.Utilities;
///
/// Utilities for handling HTTP headers.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/MiddlewareHelper.cs
similarity index 75%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareHelper.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/MiddlewareHelper.cs
index 0cf2705968f..145ab13fcdd 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareHelper.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/MiddlewareHelper.cs
@@ -1,43 +1,40 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
-using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
using HotChocolate.Language;
using Microsoft.AspNetCore.Http;
-namespace HotChocolate.AspNetCore;
+namespace HotChocolate.AspNetCore.Utilities;
internal static class MiddlewareHelper
{
public static ValidateAcceptContentTypeResult ValidateAcceptContentType(
HttpContext context,
- IHttpResponseFormatter responseFormatter,
- IServerDiagnosticEvents diagnosticEvents)
+ ExecutorSession executorSession)
{
- // first we will inspect the accept headers and determine if we can execute this request.
+ // first, we will inspect the accept-headers and determine if we can execute this request.
var headerResult = HeaderUtilities.GetAcceptHeader(context.Request);
- // if we cannot parse all media types that the user provided we will fail the request
+ // if we cannot parse all media types that the user provided, we will fail the request
// with a 400 Bad Request.
if (headerResult.HasError)
{
var errors = headerResult.ErrorResult.Errors!;
- diagnosticEvents.HttpRequestError(context, errors[0]);
+ executorSession.DiagnosticEvents.HttpRequestError(context, errors[0]);
return new ValidateAcceptContentTypeResult(
headerResult.ErrorResult,
HttpStatusCode.BadRequest);
}
- var requestFlags = responseFormatter.CreateRequestFlags(headerResult.AcceptMediaTypes);
+ var requestFlags = executorSession.ResponseFormatter.CreateRequestFlags(headerResult.AcceptMediaTypes);
// if the request defines accept header values of which we cannot handle any provided
- // media type then we will fail the request with 406 Not Acceptable.
+ // media type, then we will fail the request with 406 Not Acceptable.
if (requestFlags is RequestFlags.None)
{
var error = ErrorHelper.NoSupportedAcceptMediaType();
- diagnosticEvents.HttpRequestError(context, error);
+ executorSession.DiagnosticEvents.HttpRequestError(context, error);
return new ValidateAcceptContentTypeResult(
error,
@@ -52,31 +49,29 @@ public static ValidateAcceptContentTypeResult ValidateAcceptContentType(
public static ParseRequestResult ParseRequestFromParams(
HttpContext context,
- IHttpRequestParser requestParser,
- IErrorHandler errorHandler,
- IServerDiagnosticEvents diagnosticEvents)
+ ExecutorSession executorSession)
{
- using (diagnosticEvents.ParseHttpRequest(context))
+ using (executorSession.DiagnosticEvents.ParseHttpRequest(context))
{
try
{
return new ParseRequestResult(
- requestParser.ParseRequestFromParams(
+ executorSession.RequestParser.ParseRequestFromParams(
context.Request.Query));
}
catch (GraphQLRequestException ex)
{
// A GraphQL request exception is thrown if the HTTP request body couldn't be
- // parsed. In this case we will return HTTP status code 400 and return a
+ // parsed. In this case, we will return HTTP status code 400 and return a
// GraphQL error result.
- var errors = errorHandler.Handle(ex.Errors);
- diagnosticEvents.ParserErrors(context, errors);
+ var errors = executorSession.Handle(ex.Errors);
+ executorSession.DiagnosticEvents.ParserErrors(context, errors);
return new ParseRequestResult(errors, HttpStatusCode.BadRequest);
}
catch (Exception ex)
{
var error = ErrorBuilder.FromException(ex).Build();
- diagnosticEvents.HttpRequestError(context, error);
+ executorSession.DiagnosticEvents.HttpRequestError(context, error);
return new ParseRequestResult(error, HttpStatusCode.InternalServerError);
}
}
@@ -86,16 +81,14 @@ public static ParseRequestResult ParseVariablesAndExtensionsFromParams(
string operationId,
string? operationName,
HttpContext context,
- IHttpRequestParser requestParser,
- IErrorHandler errorHandler,
- IServerDiagnosticEvents diagnosticEvents)
+ ExecutorSession executorSession)
{
- using (diagnosticEvents.ParseHttpRequest(context))
+ using (executorSession.DiagnosticEvents.ParseHttpRequest(context))
{
try
{
return new ParseRequestResult(
- requestParser.ParsePersistedOperationRequestFromParams(
+ executorSession.RequestParser.ParsePersistedOperationRequestFromParams(
operationId,
operationName,
context.Request.Query));
@@ -103,16 +96,16 @@ public static ParseRequestResult ParseVariablesAndExtensionsFromParams(
catch (GraphQLRequestException ex)
{
// A GraphQL request exception is thrown if the HTTP request body couldn't be
- // parsed. In this case we will return HTTP status code 400 and return a
+ // parsed. In this case, we will return HTTP status code 400 and return a
// GraphQL error result.
- var errors = errorHandler.Handle(ex.Errors);
- diagnosticEvents.ParserErrors(context, errors);
+ var errors = executorSession.Handle(ex.Errors);
+ executorSession.DiagnosticEvents.ParserErrors(context, errors);
return new ParseRequestResult(errors, HttpStatusCode.BadRequest);
}
catch (Exception ex)
{
var error = ErrorBuilder.FromException(ex).Build();
- diagnosticEvents.HttpRequestError(context, error);
+ executorSession.DiagnosticEvents.HttpRequestError(context, error);
return new ParseRequestResult(error, HttpStatusCode.InternalServerError);
}
}
@@ -122,17 +115,15 @@ public static async Task ParseSingleRequestFromBodyAsync(
string operationId,
string? operationName,
HttpContext context,
- IHttpRequestParser requestParser,
- IErrorHandler errorHandler,
- IServerDiagnosticEvents diagnosticEvents)
+ ExecutorSession executorSession)
{
GraphQLRequest request;
- using (diagnosticEvents.ParseHttpRequest(context))
+ using (executorSession.DiagnosticEvents.ParseHttpRequest(context))
{
try
{
request =
- await requestParser.ParsePersistedOperationRequestAsync(
+ await executorSession.RequestParser.ParsePersistedOperationRequestAsync(
operationId,
operationName,
context.Request.Body,
@@ -141,16 +132,16 @@ await requestParser.ParsePersistedOperationRequestAsync(
catch (GraphQLRequestException ex)
{
// A GraphQL request exception is thrown if the HTTP request body couldn't be
- // parsed. In this case we will return HTTP status code 400 and return a
+ // parsed. In this case, we will return HTTP status code 400 and return a
// GraphQL error result.
- var errors = errorHandler.Handle(ex.Errors);
- diagnosticEvents.ParserErrors(context, errors);
+ var errors = executorSession.Handle(ex.Errors);
+ executorSession.DiagnosticEvents.ParserErrors(context, errors);
return new ParseRequestResult(errors, HttpStatusCode.BadRequest);
}
catch (Exception ex)
{
var error = ErrorBuilder.FromException(ex).Build();
- diagnosticEvents.HttpRequestError(context, error);
+ executorSession.DiagnosticEvents.HttpRequestError(context, error);
return new ParseRequestResult(error, HttpStatusCode.InternalServerError);
}
}
@@ -204,27 +195,23 @@ public static async Task ExecuteRequestAsync(
GraphQLRequest request,
RequestFlags flags,
HttpContext context,
- IRequestExecutor requestExecutor,
- IErrorHandler errorHandler,
- IServerDiagnosticEvents diagnosticEvents)
+ ExecutorSession executorSession)
{
- // after successfully parsing the request we now will attempt to execute the request.
- var requestInterceptor = requestExecutor.GetRequestInterceptor();
+ // after successfully parsing the request, we now will attempt to execute the request.
try
{
- diagnosticEvents.StartSingleRequest(context, request);
+ executorSession.DiagnosticEvents.StartSingleRequest(context, request);
var requestBuilder = OperationRequestBuilder.From(request);
requestBuilder.SetFlags(flags);
- await requestInterceptor.OnCreateAsync(
+ await executorSession.OnCreateAsync(
context,
- requestExecutor,
requestBuilder,
context.RequestAborted);
- var result = await requestExecutor.ExecuteAsync(
+ var result = await executorSession.ExecuteAsync(
requestBuilder.Build(),
context.RequestAborted);
@@ -232,11 +219,11 @@ await requestInterceptor.OnCreateAsync(
}
catch (GraphQLException ex)
{
- // This allows extensions to throw GraphQL exceptions in the GraphQL interceptor.
+ // this allows extensions to throw GraphQL exceptions in the GraphQL interceptor.
// we let the serializer determine the status code.
foreach (var error in ex.Errors)
{
- diagnosticEvents.HttpRequestError(context, error);
+ executorSession.DiagnosticEvents.HttpRequestError(context, error);
}
return new ExecuteRequestResult(
@@ -245,7 +232,7 @@ await requestInterceptor.OnCreateAsync(
catch (Exception ex)
{
var error = ErrorBuilder.FromException(ex).Build();
- diagnosticEvents.HttpRequestError(context, error);
+ executorSession.DiagnosticEvents.HttpRequestError(context, error);
return new ExecuteRequestResult(
OperationResultBuilder.CreateError(error),
HttpStatusCode.InternalServerError);
@@ -257,33 +244,32 @@ public static async Task WriteResultAsync(
AcceptMediaType[] acceptMediaTypes,
HttpStatusCode? statusCode,
HttpContext context,
- IHttpResponseFormatter responseFormatter,
- IServerDiagnosticEvents diagnosticEvents)
+ ExecutorSession executorSession)
{
- // query results use pooled memory an need to be disposed
+ // query results use pooled memory a need to be disposed
// after we are finished with hem.
await using var result = executionResult;
IDisposable? formatScope = null;
try
{
- // if cancellation is requested we will not try to attempt to write the result to the
+ // if cancellation is requested, we will not try to attempt to write the result to the
// response stream.
if (context.RequestAborted.IsCancellationRequested)
{
return;
}
- // in any case we will have a valid GraphQL result at this point that can be written
+ // in any case, we will have a valid GraphQL result at this point that can be written
// to the HTTP response stream.
Debug.Assert(result is not null, "No GraphQL result was created.");
if (result is IOperationResult queryResult)
{
- formatScope = diagnosticEvents.FormatHttpResponse(context, queryResult);
+ formatScope = executorSession.DiagnosticEvents.FormatHttpResponse(context, queryResult);
}
- await responseFormatter.FormatAsync(
+ await executorSession.ResponseFormatter.FormatAsync(
context.Response,
result,
acceptMediaTypes,
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ThrowHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/ThrowHelper.cs
similarity index 97%
rename from src/HotChocolate/AspNetCore/src/AspNetCore/ThrowHelper.cs
rename to src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/ThrowHelper.cs
index 1df66fd38b2..8e12a5a1183 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/ThrowHelper.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Utilities/ThrowHelper.cs
@@ -1,7 +1,7 @@
using HotChocolate.Language;
-using static HotChocolate.AspNetCore.Properties.AspNetCoreResources;
+using static HotChocolate.AspNetCore.Properties.AspNetCorePipelineResources;
-namespace HotChocolate.AspNetCore;
+namespace HotChocolate.AspNetCore.Utilities;
///
/// An internal helper class that centralizes the server exceptions.
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/WebSocketSubscriptionMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/WebSocketSubscriptionMiddleware.cs
new file mode 100644
index 00000000000..7d4cfe100bd
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/WebSocketSubscriptionMiddleware.cs
@@ -0,0 +1,39 @@
+using HotChocolate.AspNetCore.Subscriptions;
+using Microsoft.AspNetCore.Http;
+using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
+
+namespace HotChocolate.AspNetCore;
+
+public sealed class WebSocketSubscriptionMiddleware : MiddlewareBase
+{
+ public WebSocketSubscriptionMiddleware(
+ HttpRequestDelegate next,
+ HttpRequestExecutorProxy executor)
+ : base(next, executor)
+ {
+ }
+
+ public Task InvokeAsync(HttpContext context)
+ {
+ return context.WebSockets.IsWebSocketRequest
+ ? HandleWebSocketSessionAsync(context)
+ : NextAsync(context);
+ }
+
+ private async Task HandleWebSocketSessionAsync(HttpContext context)
+ {
+ var session = await Executor.GetOrCreateSessionAsync(context.RequestAborted);
+
+ using (session.DiagnosticEvents.WebSocketSession(context))
+ {
+ try
+ {
+ await WebSocketSession.AcceptAsync(context, session);
+ }
+ catch (Exception ex)
+ {
+ session.DiagnosticEvents.WebSocketSessionError(context, ex);
+ }
+ }
+ }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/DelegateHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/DelegateHttpRequestInterceptor.cs
deleted file mode 100644
index ba265326a39..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/DelegateHttpRequestInterceptor.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Microsoft.AspNetCore.Http;
-
-namespace HotChocolate.AspNetCore;
-
-internal sealed class DelegateHttpRequestInterceptor : DefaultHttpRequestInterceptor
-{
- private readonly HttpRequestInterceptorDelegate _interceptor;
-
- public DelegateHttpRequestInterceptor(HttpRequestInterceptorDelegate interceptor)
- {
- _interceptor = interceptor ?? throw new ArgumentNullException(nameof(interceptor));
- }
-
- public override async ValueTask OnCreateAsync(
- HttpContext context,
- IRequestExecutor requestExecutor,
- OperationRequestBuilder requestBuilder,
- CancellationToken cancellationToken)
- {
- await _interceptor(context, requestExecutor, requestBuilder, cancellationToken);
- await base.OnCreateAsync(context, requestExecutor, requestBuilder, cancellationToken);
- }
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs
index 5c7ffaa5c1d..914cfe828eb 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs
@@ -1,9 +1,10 @@
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection.Extensions;
using HotChocolate.AspNetCore;
-using HotChocolate.AspNetCore.Serialization;
+using HotChocolate.AspNetCore.Formatters;
using HotChocolate.Execution.Configuration;
using HotChocolate.Transport.Formatters;
+using Microsoft.AspNetCore.Http;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;
@@ -11,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection;
public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
{
///
- /// Adds an interceptor for GraphQL requests to the GraphQL configuration.
+ /// Adds an interceptor for GraphQL over HTTP requests to the GraphQL configuration.
///
///
/// The .
@@ -24,14 +25,18 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
///
public static IRequestExecutorBuilder AddHttpRequestInterceptor(
this IRequestExecutorBuilder builder)
- where T : class, IHttpRequestInterceptor =>
- builder.ConfigureSchemaServices(s => s
+ where T : class, IHttpRequestInterceptor
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.ConfigureSchemaServices(s => s
.RemoveAll()
- .AddSingleton(
- sp => ActivatorUtilities.GetServiceOrCreateInstance(sp.GetCombinedServices())));
+ .AddSingleton(sp =>
+ ActivatorUtilities.GetServiceOrCreateInstance(sp.GetCombinedServices())));
+ }
///
- /// Adds an interceptor for GraphQL requests to the GraphQL configuration.
+ /// Adds an interceptor for GraphQL over HTTP requests to the GraphQL configuration.
///
///
/// The .
@@ -48,43 +53,46 @@ public static IRequestExecutorBuilder AddHttpRequestInterceptor(
public static IRequestExecutorBuilder AddHttpRequestInterceptor(
this IRequestExecutorBuilder builder,
Func factory)
- where T : class, IHttpRequestInterceptor =>
- builder.ConfigureSchemaServices(s => s
+ where T : class, IHttpRequestInterceptor
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(factory);
+
+ return builder.ConfigureSchemaServices(s => s
.RemoveAll()
.AddSingleton(sp => factory(sp.GetCombinedServices())));
+ }
///
- /// Adds an interceptor for GraphQL requests to the GraphQL configuration.
+ /// Adds an interceptor for GraphQL over HTTP requests to the GraphQL configuration.
///
///
/// The .
///
- ///
- /// The interceptor instance that shall be added to the configuration.
+ ///
+ /// A delegate that allows to configure the GraphQL request.
///
- ///
- /// Returns the so that configuration can be chained.
- ///
+ ///
public static IRequestExecutorBuilder AddHttpRequestInterceptor(
this IRequestExecutorBuilder builder,
- HttpRequestInterceptorDelegate interceptor) =>
- AddHttpRequestInterceptor(
- builder,
- _ => new DelegateHttpRequestInterceptor(interceptor));
+ Func handler)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.AddHttpRequestInterceptor(_ => new DelegateHttpRequestInterceptor(handler));
+ }
private static IRequestExecutorBuilder AddDefaultHttpRequestInterceptor(
this IRequestExecutorBuilder builder)
- {
- return builder.ConfigureSchemaServices(
+ => builder.ConfigureSchemaServices(
s => s.TryAddSingleton());
- }
///
/// Adds the with specific formatter options
/// to the DI.
///
- ///
- /// The .
+ ///
+ /// The .
///
///
/// Defines whether the underlying
@@ -94,56 +102,62 @@ private static IRequestExecutorBuilder AddDefaultHttpRequestInterceptor(
/// By default, the JSON is written without extra white spaces.
///
///
- /// Returns the so that configuration can be chained.
+ /// Returns the so that configuration can be chained.
///
- public static IServiceCollection AddHttpResponseFormatter(
- this IServiceCollection services,
+ public static IRequestExecutorBuilder AddHttpResponseFormatter(
+ this IRequestExecutorBuilder builder,
bool indented = false)
{
- services.RemoveAll();
- services.AddSingleton(
- sp => DefaultHttpResponseFormatter.Create(
- new HttpResponseFormatterOptions
- {
- Json = new JsonResultFormatterOptions
- {
- Indented = indented
- }
- },
- sp.GetRequiredService()));
- return services;
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.ConfigureSchemaServices(
+ s => s.RemoveAll()
+ .AddSingleton(
+ sp => DefaultHttpResponseFormatter.Create(
+ new HttpResponseFormatterOptions
+ {
+ Json = new JsonResultFormatterOptions
+ {
+ Indented = indented
+ }
+ },
+ sp.GetRequiredService())));
+ return builder;
}
///
/// Adds the with specific formatter options
/// to the DI.
///
- ///
- /// The .
+ ///
+ /// The .
///
///
/// The HTTP response formatter options
///
///
- /// Returns the so that configuration can be chained.
+ /// Returns the so that configuration can be chained.
///
- public static IServiceCollection AddHttpResponseFormatter(
- this IServiceCollection services,
+ public static IRequestExecutorBuilder AddHttpResponseFormatter(
+ this IRequestExecutorBuilder builder,
HttpResponseFormatterOptions options)
{
- services.RemoveAll();
- services.AddSingleton(
- sp => DefaultHttpResponseFormatter.Create(
- options,
- sp.GetRequiredService()));
- return services;
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.ConfigureSchemaServices(
+ s => s.RemoveAll()
+ .AddSingleton(
+ sp => DefaultHttpResponseFormatter.Create(
+ options,
+ sp.GetRequiredService())));
+ return builder;
}
///
/// Adds a custom HTTP response formatter to the DI.
///
- ///
- /// The .
+ ///
+ /// The .
///
///
/// The type of the custom .
@@ -151,20 +165,23 @@ public static IServiceCollection AddHttpResponseFormatter(
///
/// Returns the so that configuration can be chained.
///
- public static IServiceCollection AddHttpResponseFormatter(
- this IServiceCollection services)
+ public static IRequestExecutorBuilder AddHttpResponseFormatter(
+ this IRequestExecutorBuilder builder)
where T : class, IHttpResponseFormatter
{
- services.RemoveAll();
- services.AddSingleton();
- return services;
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.ConfigureSchemaServices(
+ s => s.RemoveAll()
+ .AddSingleton());
+ return builder;
}
///
/// Adds a custom HTTP response formatter to the DI.
///
- ///
- /// The .
+ ///
+ /// The .
///
///
/// The service factory.
@@ -175,13 +192,17 @@ public static IServiceCollection AddHttpResponseFormatter(
///
/// Returns the so that configuration can be chained.
///
- public static IServiceCollection AddHttpResponseFormatter(
- this IServiceCollection services,
+ public static IRequestExecutorBuilder AddHttpResponseFormatter(
+ this IRequestExecutorBuilder builder,
Func factory)
where T : class, IHttpResponseFormatter
{
- services.RemoveAll();
- services.AddSingleton(factory);
- return services;
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(factory);
+
+ builder.ConfigureSchemaServices(
+ s => s.RemoveAll()
+ .AddSingleton(factory));
+ return builder;
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs
index e2d022b8238..cbacdb1360e 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using HotChocolate.AspNetCore;
-using HotChocolate.AspNetCore.Serialization;
+using HotChocolate.AspNetCore.Formatters;
using HotChocolate.AspNetCore.Subscriptions.Protocols;
using HotChocolate.AspNetCore.Subscriptions.Protocols.Apollo;
using HotChocolate.AspNetCore.Subscriptions.Protocols.GraphQLOverWebSocket;
@@ -60,9 +60,7 @@ private static IRequestExecutorBuilder AddSubscriptionServices(
.ConfigureSchemaServices(s =>
{
s.TryAddSingleton();
- s.TryAddSingleton(
- _ => new DefaultWebSocketPayloadFormatter(
- new WebSocketPayloadFormatterOptions()));
+ s.TryAddSingleton(_ => new DefaultWebSocketPayloadFormatter());
})
.AddApolloProtocol()
.AddGraphQLOverWebSocketProtocol();
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs
index 28cd5010c47..a72fe40c7b4 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs
@@ -1,8 +1,9 @@
using HotChocolate.AspNetCore;
+using HotChocolate.AspNetCore.Formatters;
using Microsoft.Extensions.DependencyInjection.Extensions;
using HotChocolate.AspNetCore.Instrumentation;
using HotChocolate.AspNetCore.ParameterExpressionBuilders;
-using HotChocolate.AspNetCore.Serialization;
+using HotChocolate.AspNetCore.Parsers;
using HotChocolate.Execution.Configuration;
using HotChocolate.Internal;
using HotChocolate.Language;
@@ -18,65 +19,51 @@ namespace Microsoft.Extensions.DependencyInjection;
///
public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
{
- ///
- /// Adds the GraphQL server core services to the DI.
- ///
- ///
- /// The .
- ///
- ///
- /// The max allowed GraphQL request size.
- ///
- ///
- /// Returns the so that configuration can be chained.
- ///
- ///
- /// The is null.
- ///
- public static IServiceCollection AddGraphQLServerCore(
- this IServiceCollection services,
+ private static IRequestExecutorBuilder AddGraphQLServerCore(
+ this IRequestExecutorBuilder builder,
int maxAllowedRequestSize = MaxAllowedRequestSize)
{
- ArgumentNullException.ThrowIfNull(services);
-
- services.AddGraphQLCore();
- services.TryAddSingleton(
- sp => DefaultHttpResponseFormatter.Create(
- new HttpResponseFormatterOptions { HttpTransportVersion = HttpTransportVersion.Latest },
- sp.GetRequiredService()));
- services.TryAddSingleton(
- sp => new DefaultHttpRequestParser(
- sp.GetRequiredService(),
- sp.GetRequiredService(),
- maxAllowedRequestSize,
- sp.GetRequiredService()));
- services.TryAddSingleton(sp =>
+ builder.ConfigureSchemaServices(s =>
{
- var listeners = sp.GetServices().ToArray();
- return listeners.Length switch
+ s.TryAddSingleton(
+ sp => DefaultHttpResponseFormatter.Create(
+ new HttpResponseFormatterOptions { HttpTransportVersion = HttpTransportVersion.Latest },
+ sp.GetRequiredService()));
+ s.TryAddSingleton(
+ sp => new DefaultHttpRequestParser(
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ maxAllowedRequestSize,
+ sp.GetRequiredService()));
+
+ s.TryAddSingleton(sp =>
{
- 0 => new NoopServerDiagnosticEventListener(),
- 1 => listeners[0],
- _ => new AggregateServerDiagnosticEventListener(listeners)
- };
+ var listeners = sp.GetServices().ToArray();
+ return listeners.Length switch
+ {
+ 0 => new NoopServerDiagnosticEventListener(),
+ 1 => listeners[0],
+ _ => new AggregateServerDiagnosticEventListener(listeners)
+ };
+ });
});
- if (!services.IsImplementationTypeRegistered())
+ if (!builder.Services.IsImplementationTypeRegistered())
{
- services.AddSingleton();
+ builder.Services.AddSingleton();
}
- if (!services.IsImplementationTypeRegistered())
+ if (!builder.Services.IsImplementationTypeRegistered())
{
- services.AddSingleton();
+ builder.Services.AddSingleton();
}
- if (!services.IsImplementationTypeRegistered())
+ if (!builder.Services.IsImplementationTypeRegistered())
{
- services.AddSingleton();
+ builder.Services.AddSingleton();
}
- return services;
+ return builder;
}
///
@@ -106,8 +93,8 @@ public static IRequestExecutorBuilder AddGraphQLServer(
ArgumentNullException.ThrowIfNull(services);
var builder = services
- .AddGraphQLServerCore(maxAllowedRequestSize)
.AddGraphQL(schemaName)
+ .AddGraphQLServerCore(maxAllowedRequestSize)
.AddDefaultHttpRequestInterceptor()
.AddSubscriptionServices();
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs
deleted file mode 100644
index d487618bb1e..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using Microsoft.AspNetCore.Http;
-using static System.Globalization.CultureInfo;
-using static System.String;
-
-namespace HotChocolate.AspNetCore;
-
-internal static class HttpResponseExtensions
-{
- private const string ContentDispositionHeader = "Content-Disposition";
- private const string ContentDispositionValue = "attachment; filename=\"{0}\"";
- private static readonly JsonSerializerOptions s_serializerOptions = new()
- {
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- };
-
- internal static Task WriteAsJsonAsync(
- this HttpResponse response,
- TValue value,
- CancellationToken cancellationToken = default)
- {
- response.ContentType = ContentType.Json;
- response.StatusCode = 200;
-
- return JsonSerializer.SerializeAsync(
- response.Body,
- value,
- s_serializerOptions,
- cancellationToken);
- }
-
- public static IHeaderDictionary SetContentDisposition(
- this IHeaderDictionary headers,
- string fileName)
- {
- headers[ContentDispositionHeader] =
- Format(InvariantCulture, ContentDispositionValue, fileName);
- return headers;
- }
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs
deleted file mode 100644
index fa942d253ea..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace HotChocolate.AspNetCore;
-
-internal static class RequestExecutorExtensions
-{
- internal static T GetRequiredService(
- this IRequestExecutor requestExecutor)
- where T : notnull =>
- requestExecutor.Schema.Services.GetRequiredService();
-
- internal static IErrorHandler GetErrorHandler(
- this IRequestExecutor requestExecutor) =>
- requestExecutor.GetRequiredService();
-
- internal static IHttpRequestInterceptor GetRequestInterceptor(
- this IRequestExecutor requestExecutor) =>
- requestExecutor.GetRequiredService();
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HotChocolate.AspNetCore.csproj b/src/HotChocolate/AspNetCore/src/AspNetCore/HotChocolate.AspNetCore.csproj
index 0520a5c65ab..af5213135b3 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HotChocolate.AspNetCore.csproj
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HotChocolate.AspNetCore.csproj
@@ -19,6 +19,7 @@
+
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartRequest.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartRequest.cs
deleted file mode 100644
index 6452e05be77..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartRequest.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace HotChocolate.AspNetCore;
-
-internal sealed class HttpMultipartRequest
-{
- public HttpMultipartRequest(string operations, IDictionary fileMap)
- {
- Operations = operations;
- FileMap = fileMap;
- }
-
- public string Operations { get; set; }
-
- public IDictionary FileMap { get; set; }
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs
deleted file mode 100644
index 49ec28f7a51..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
-
-namespace HotChocolate.AspNetCore;
-
-public sealed class HttpPostMiddleware(
- HttpRequestDelegate next,
- IRequestExecutorProvider executorProvider,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IHttpRequestParser requestParser,
- IServerDiagnosticEvents diagnosticEvents,
- string schemaName)
- : HttpPostMiddlewareBase(
- next,
- executorProvider,
- executorEvents,
- responseFormatter,
- requestParser,
- diagnosticEvents,
- schemaName);
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpRequestInterceptorDelegate.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpRequestInterceptorDelegate.cs
deleted file mode 100644
index 67300851fb6..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpRequestInterceptorDelegate.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Microsoft.AspNetCore.Http;
-
-namespace HotChocolate.AspNetCore;
-
-public delegate ValueTask HttpRequestInterceptorDelegate(
- HttpContext context,
- IRequestExecutor requestExecutor,
- OperationRequestBuilder requestBuilder,
- CancellationToken cancellationToken);
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs
deleted file mode 100644
index dd22b24c2c9..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs
+++ /dev/null
@@ -1,248 +0,0 @@
-using System.Net;
-using System.Runtime.CompilerServices;
-using Microsoft.AspNetCore.Http;
-using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using HotChocolate.Language;
-using HotChocolate.Utilities;
-using RequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
-
-namespace HotChocolate.AspNetCore;
-
-///
-/// The Hot Chocolate ASP.NET core middleware base class.
-///
-public class MiddlewareBase : IDisposable
-{
- private readonly RequestDelegate _next;
- private readonly IHttpResponseFormatter _responseFormatter;
- private readonly RequestExecutorProxy _executorProxy;
- private GraphQLServerOptions? _options;
- private bool _disposed;
-
- protected MiddlewareBase(
- RequestDelegate next,
- IRequestExecutorProvider executorProvider,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- string schemaName)
- {
- ArgumentNullException.ThrowIfNull(executorProvider);
-
- _next = next ??
- throw new ArgumentNullException(nameof(next));
- _responseFormatter = responseFormatter ??
- throw new ArgumentNullException(nameof(responseFormatter));
- SchemaName = schemaName;
- IsDefaultSchema = SchemaName.EqualsOrdinal(ISchemaDefinition.DefaultName);
- _executorProxy = new RequestExecutorProxy(executorProvider, executorEvents, schemaName);
- }
-
- ///
- /// Gets the name of the schema that this middleware serves up.
- ///
- protected string SchemaName { get; }
-
- ///
- /// Specifies if this middleware handles the default schema.
- ///
- protected bool IsDefaultSchema { get; }
-
- ///
- /// Gets the request executor proxy.
- ///
- protected RequestExecutorProxy ExecutorProxy => _executorProxy;
-
- ///
- /// Gets the response formatter.
- ///
- protected IHttpResponseFormatter ResponseFormatter => _responseFormatter;
-
- ///
- /// Invokes the next middleware in line.
- ///
- ///
- /// The .
- ///
- protected Task NextAsync(HttpContext context) => _next(context);
-
- ///
- /// Gets the request executor for this middleware.
- ///
- ///
- /// The cancellation token.
- ///
- ///
- /// Returns the request executor for this middleware.
- ///
- protected ValueTask GetExecutorAsync(CancellationToken cancellationToken)
- => _executorProxy.GetExecutorAsync(cancellationToken);
-
- ///
- /// Gets the schema for this middleware.
- ///
- ///
- /// The cancellation token.
- ///
- ///
- /// Returns the schema for this middleware.
- ///
- protected async ValueTask GetSchemaAsync(CancellationToken cancellationToken)
- => await _executorProxy.GetSchemaAsync(cancellationToken);
-
- protected ValueTask WriteResultAsync(
- HttpContext context,
- IExecutionResult result,
- AcceptMediaType[] acceptMediaTypes,
- HttpStatusCode? statusCode = null)
- => _responseFormatter.FormatAsync(
- context.Response,
- result,
- acceptMediaTypes,
- statusCode,
- context.RequestAborted);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected RequestFlags CreateRequestFlags(AcceptMediaType[] acceptMediaTypes)
- => _responseFormatter.CreateRequestFlags(acceptMediaTypes);
-
- protected static async Task ExecuteSingleAsync(
- HttpContext context,
- IRequestExecutor requestExecutor,
- IHttpRequestInterceptor requestInterceptor,
- IServerDiagnosticEvents diagnosticEvents,
- GraphQLRequest request,
- RequestFlags flags)
- {
- diagnosticEvents.StartSingleRequest(context, request);
-
- var requestBuilder = OperationRequestBuilder.From(request);
- requestBuilder.SetFlags(flags);
-
- await requestInterceptor.OnCreateAsync(
- context,
- requestExecutor,
- requestBuilder,
- context.RequestAborted);
-
- return await requestExecutor.ExecuteAsync(
- requestBuilder.Build(),
- context.RequestAborted);
- }
-
- protected static async Task ExecuteOperationBatchAsync(
- HttpContext context,
- IRequestExecutor requestExecutor,
- IHttpRequestInterceptor requestInterceptor,
- IServerDiagnosticEvents diagnosticEvents,
- GraphQLRequest request,
- RequestFlags flags,
- IReadOnlyList operationNames)
- {
- diagnosticEvents.StartOperationBatchRequest(context, request, operationNames);
-
- var requestBatch = new IOperationRequest[operationNames.Count];
-
- for (var i = 0; i < operationNames.Count; i++)
- {
- var requestBuilder = OperationRequestBuilder.From(request);
- requestBuilder.SetOperationName(operationNames[i]);
- requestBuilder.SetFlags(flags);
-
- await requestInterceptor.OnCreateAsync(
- context,
- requestExecutor,
- requestBuilder,
- context.RequestAborted);
-
- requestBatch[i] = requestBuilder.Build();
- }
-
- return await requestExecutor.ExecuteBatchAsync(
- new OperationRequestBatch(requestBatch, services: context.RequestServices),
- cancellationToken: context.RequestAborted);
- }
-
- protected static async Task ExecuteBatchAsync(
- HttpContext context,
- IRequestExecutor requestExecutor,
- IHttpRequestInterceptor requestInterceptor,
- IServerDiagnosticEvents diagnosticEvents,
- IReadOnlyList requests,
- RequestFlags flags)
- {
- diagnosticEvents.StartBatchRequest(context, requests);
-
- var requestBatch = new IOperationRequest[requests.Count];
-
- for (var i = 0; i < requests.Count; i++)
- {
- var requestBuilder = OperationRequestBuilder.From(requests[i]);
- requestBuilder.SetFlags(flags);
-
- await requestInterceptor.OnCreateAsync(
- context,
- requestExecutor,
- requestBuilder,
- context.RequestAborted);
-
- requestBatch[i] = requestBuilder.Build();
- }
-
- return await requestExecutor.ExecuteBatchAsync(
- new OperationRequestBatch(requestBatch, services: context.RequestServices),
- cancellationToken: context.RequestAborted);
- }
-
- protected static RequestContentType ParseContentType(HttpContext context)
- {
- if (context.Items.TryGetValue(nameof(RequestContentType), out var value) &&
- value is RequestContentType contentType)
- {
- return contentType;
- }
-
- var span = context.Request.ContentType.AsSpan();
-
- if (span.StartsWith(ContentType.JsonSpan()))
- {
- context.Items[nameof(RequestContentType)] = RequestContentType.Json;
- return RequestContentType.Json;
- }
-
- if (span.StartsWith(ContentType.MultiPartFormSpan()))
- {
- context.Items[nameof(RequestContentType)] = RequestContentType.Form;
- return RequestContentType.Form;
- }
-
- context.Items[nameof(RequestContentType)] = RequestContentType.None;
- return RequestContentType.None;
- }
-
- protected GraphQLServerOptions GetOptions(HttpContext context)
- {
- if (_options is not null)
- {
- return _options;
- }
-
- _options = context.GetGraphQLServerOptions() ?? new GraphQLServerOptions();
- return _options;
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed && disposing)
- {
- ExecutorProxy.Dispose();
- _disposed = true;
- }
- }
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/PersistedOperationMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/PersistedOperationMiddleware.cs
deleted file mode 100644
index 180709151c0..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/PersistedOperationMiddleware.cs
+++ /dev/null
@@ -1,252 +0,0 @@
-using System.Net;
-using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-
-namespace HotChocolate.AspNetCore;
-
-internal static class PersistedOperationMiddleware
-{
- internal static void MapPersistedOperationMiddleware(
- this RouteGroupBuilder groupBuilder,
- string schemaName,
- bool requireOperationName)
- {
- var state = new State(schemaName);
-
- groupBuilder
- .MapGet(
- "/{OperationId}",
- ([AsParameters] Services services, string operationId)
- => ExecuteGetRequestAsync(state, services, operationId, null, requireOperationName));
-
- groupBuilder
- .MapGet(
- "/{OperationId}/{OperationName}",
- ([AsParameters] Services services, string operationId, string operationName)
- => ExecuteGetRequestAsync(state, services, operationId, operationName, requireOperationName));
-
- groupBuilder
- .MapPost(
- "/{OperationId}",
- ([AsParameters] Services services, string operationId)
- => ExecutePostRequestAsync(state, services, operationId, null, requireOperationName));
-
- groupBuilder
- .MapPost(
- "/{OperationId}/{OperationName}",
- ([AsParameters] Services services, string operationId, string operationName)
- => ExecutePostRequestAsync(state, services, operationId, operationName, requireOperationName));
- }
-
- private static async Task ExecuteGetRequestAsync(
- State state,
- Services services,
- string operationId,
- string? operationName,
- bool requireOperationName)
- {
- HttpStatusCode? statusCode;
- IExecutionResult? result;
-
- // first we validate the accept headers.
- var validationResult =
- MiddlewareHelper.ValidateAcceptContentType(
- services.Context,
- services.ResponseFormatter,
- services.DiagnosticEvents);
-
- var acceptMediaTypes = validationResult.AcceptMediaTypes;
-
- if (!validationResult.IsValid)
- {
- statusCode = validationResult.StatusCode.Value;
- result = validationResult.Error;
- goto HANDLE_RESULT;
- }
-
- // validate if the operation name is required.
- if (requireOperationName && string.IsNullOrWhiteSpace(operationName))
- {
- statusCode = HttpStatusCode.BadRequest;
- result = ErrorHelper.OperationNameRequired();
- goto HANDLE_RESULT;
- }
-
- // next we parse the GraphQL request.
- var executor = state.CurrentExecutor
- ?? await state.GetExecutorAsync(services);
- var errorHandler = executor.GetErrorHandler();
-
- var parserResult =
- MiddlewareHelper.ParseVariablesAndExtensionsFromParams(
- operationId,
- operationName,
- services.Context,
- services.RequestParser,
- errorHandler,
- services.DiagnosticEvents);
-
- if (!parserResult.IsValid)
- {
- statusCode = parserResult.StatusCode.Value;
- result = parserResult.Error;
- goto HANDLE_RESULT;
- }
-
- // before we can execute the request we need to determine the request flags.
- var request = parserResult.Request!;
- var options = state.GetOptions(services.Context);
- var requestFlags =
- MiddlewareHelper.DetermineHttpGetRequestFlags(
- validationResult.RequestFlags,
- options);
-
- // next we will execute the request.
- var executionResult =
- await MiddlewareHelper.ExecuteRequestAsync(
- request,
- requestFlags,
- services.Context,
- executor,
- errorHandler,
- services.DiagnosticEvents);
- statusCode = executionResult.StatusCode;
- result = executionResult.Result;
-
-HANDLE_RESULT:
- await MiddlewareHelper.WriteResultAsync(
- result!,
- acceptMediaTypes,
- statusCode,
- services.Context,
- services.ResponseFormatter,
- services.DiagnosticEvents);
- }
-
- private static async Task ExecutePostRequestAsync(
- State state,
- Services services,
- string operationId,
- string? operationName,
- bool requireOperationName)
- {
- HttpStatusCode? statusCode;
- IExecutionResult? result;
-
- // first we validate the accept headers.
- var validationResult =
- MiddlewareHelper.ValidateAcceptContentType(
- services.Context,
- services.ResponseFormatter,
- services.DiagnosticEvents);
-
- var acceptMediaTypes = validationResult.AcceptMediaTypes;
-
- if (!validationResult.IsValid)
- {
- statusCode = validationResult.StatusCode.Value;
- result = validationResult.Error;
- goto HANDLE_RESULT;
- }
-
- // validate if the operation name is required.
- if (requireOperationName && string.IsNullOrWhiteSpace(operationName))
- {
- statusCode = HttpStatusCode.BadRequest;
- result = ErrorHelper.OperationNameRequired();
- goto HANDLE_RESULT;
- }
-
- // next we parse the GraphQL request.
- var executor = state.CurrentExecutor ?? await state.GetExecutorAsync(services);
- var errorHandler = executor.GetErrorHandler();
-
- var parserResult =
- await MiddlewareHelper.ParseSingleRequestFromBodyAsync(
- operationId,
- operationName,
- services.Context,
- services.RequestParser,
- errorHandler,
- services.DiagnosticEvents);
-
- if (!parserResult.IsValid)
- {
- statusCode = parserResult.StatusCode.Value;
- result = parserResult.Error;
- goto HANDLE_RESULT;
- }
-
- // after successfully parsing the request we now will attempt to execute the request.
- var executionResult =
- await MiddlewareHelper.ExecuteRequestAsync(
- parserResult.Request!,
- validationResult.RequestFlags,
- services.Context,
- executor,
- errorHandler,
- services.DiagnosticEvents);
- statusCode = executionResult.StatusCode;
- result = executionResult.Result;
-
-HANDLE_RESULT:
- await MiddlewareHelper.WriteResultAsync(
- result!,
- acceptMediaTypes,
- statusCode,
- services.Context,
- services.ResponseFormatter,
- services.DiagnosticEvents);
- }
-
- private sealed class Services(
- HttpContext context,
- IRequestExecutorProvider executorProvider,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IHttpRequestParser requestParser,
- IServerDiagnosticEvents diagnosticEvents)
- {
- public HttpContext Context { get; } = context;
-
- public IRequestExecutorProvider ExecutorProvider { get; } = executorProvider;
-
- public IRequestExecutorEvents ExecutorEvents { get; } = executorEvents;
-
- public IHttpResponseFormatter ResponseFormatter { get; } = responseFormatter;
-
- public IHttpRequestParser RequestParser { get; } = requestParser;
-
- public IServerDiagnosticEvents DiagnosticEvents { get; } = diagnosticEvents;
-
- public CancellationToken RequestAborted => Context.RequestAborted;
- }
-
- private sealed class State(string schemaName)
- {
- private RequestExecutorProxy? _proxy;
- private GraphQLServerOptions? _options;
-
- public IRequestExecutor? CurrentExecutor => _proxy?.CurrentExecutor;
-
- public ValueTask GetExecutorAsync(Services services)
- {
- _proxy ??= new RequestExecutorProxy(services.ExecutorProvider, services.ExecutorEvents, schemaName);
- return _proxy.GetExecutorAsync(services.RequestAborted);
- }
-
- public GraphQLServerOptions GetOptions(HttpContext context)
- {
- if (_options is not null)
- {
- return _options;
- }
-
- _options = context.GetGraphQLServerOptions() ?? new GraphQLServerOptions();
- return _options;
- }
- }
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Resources/.keep b/src/HotChocolate/AspNetCore/src/AspNetCore/Resources/.keep
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Resources/index.html b/src/HotChocolate/AspNetCore/src/AspNetCore/Resources/index.html
deleted file mode 100644
index 18ecdcb795c..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Resources/index.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ConnectionContextKeys.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ConnectionContextKeys.cs
deleted file mode 100644
index d30c99f477e..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/ConnectionContextKeys.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace HotChocolate.AspNetCore.Subscriptions;
-
-internal static class ConnectionContextKeys
-{
- public const string Connected = "HotChocolate.WebSockets.Connected";
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8Messages.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8Messages.cs
deleted file mode 100644
index a820a01a444..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/Apollo/Utf8Messages.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-namespace HotChocolate.AspNetCore.Subscriptions.Protocols.Apollo;
-
-internal static class Utf8Messages
-{
- // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
- public static ReadOnlySpan ConnectionInitialize =>
- [
- (byte)'c',
- (byte)'o',
- (byte)'n',
- (byte)'n',
- (byte)'e',
- (byte)'c',
- (byte)'t',
- (byte)'i',
- (byte)'o',
- (byte)'n',
- (byte)'_',
- (byte)'i',
- (byte)'n',
- (byte)'i',
- (byte)'t'
- ];
-
- public static ReadOnlySpan ConnectionAccept =>
- [
- (byte)'c',
- (byte)'o',
- (byte)'n',
- (byte)'n',
- (byte)'e',
- (byte)'c',
- (byte)'t',
- (byte)'i',
- (byte)'o',
- (byte)'n',
- (byte)'_',
- (byte)'a',
- (byte)'c',
- (byte)'k'
- ];
-
- public static ReadOnlySpan ConnectionError =>
- [
- (byte)'c',
- (byte)'o',
- (byte)'n',
- (byte)'n',
- (byte)'e',
- (byte)'c',
- (byte)'t',
- (byte)'i',
- (byte)'o',
- (byte)'n',
- (byte)'_',
- (byte)'e',
- (byte)'r',
- (byte)'r',
- (byte)'o',
- (byte)'r'
- ];
-
- public static ReadOnlySpan ConnectionTerminate =>
- [
- (byte)'c',
- (byte)'o',
- (byte)'n',
- (byte)'n',
- (byte)'e',
- (byte)'c',
- (byte)'t',
- (byte)'i',
- (byte)'o',
- (byte)'n',
- (byte)'_',
- (byte)'t',
- (byte)'e',
- (byte)'r',
- (byte)'m',
- (byte)'i',
- (byte)'n',
- (byte)'a',
- (byte)'t',
- (byte)'e'
- ];
-
- public static ReadOnlySpan Start =>
- [
- (byte)'s',
- (byte)'t',
- (byte)'a',
- (byte)'r',
- (byte)'t'
- ];
-
- public static ReadOnlySpan Stop =>
- [
- (byte)'s',
- (byte)'t',
- (byte)'o',
- (byte)'p'
- ];
-
- public static ReadOnlySpan Data =>
- [
- (byte)'d',
- (byte)'a',
- (byte)'t',
- (byte)'a'
- ];
-
- public static ReadOnlySpan Error =>
- [
- (byte)'e',
- (byte)'r',
- (byte)'r',
- (byte)'o',
- (byte)'r'
- ];
-
- public static ReadOnlySpan Complete =>
- [
- (byte)'c',
- (byte)'o',
- (byte)'m',
- (byte)'p',
- (byte)'l',
- (byte)'e',
- (byte)'t',
- (byte)'e'
- ];
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/IOperationMessagePayload.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/IOperationMessagePayload.cs
deleted file mode 100644
index 74e046c9531..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Protocols/IOperationMessagePayload.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace HotChocolate.AspNetCore.Subscriptions.Protocols;
-
-///
-/// The operation message with a custom payload.
-///
-public interface IOperationMessagePayload
-{
- ///
- /// Deserializes the payload as the specified type.
- ///
- ///
- /// The type as which the payload shall be deserialized.
- ///
- ///
- /// Returns the deserialized payload.
- ///
- T? As() where T : class;
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/UploadedFile.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/UploadedFile.cs
deleted file mode 100644
index 7b1c021121f..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/UploadedFile.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.AspNetCore.Http;
-
-namespace HotChocolate.AspNetCore;
-
-internal sealed class UploadedFile : StreamFile
-{
- private readonly IFormFile _file;
-
- public UploadedFile(IFormFile file)
- : base(file.FileName, file.OpenReadStream, file.Length, file.ContentType)
- {
- _file = file ?? throw new ArgumentNullException(nameof(file));
- }
-
- public override Task CopyToAsync(
- Stream target,
- CancellationToken cancellationToken = default) =>
- _file.CopyToAsync(target, cancellationToken);
-}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs
deleted file mode 100644
index 90076f1a88d..00000000000
--- a/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using Microsoft.AspNetCore.Http;
-using HotChocolate.AspNetCore.Instrumentation;
-using HotChocolate.AspNetCore.Serialization;
-using HotChocolate.AspNetCore.Subscriptions;
-using RequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
-
-namespace HotChocolate.AspNetCore;
-
-public sealed class WebSocketSubscriptionMiddleware : MiddlewareBase
-{
- private readonly IServerDiagnosticEvents _diagnosticEvents;
-
- public WebSocketSubscriptionMiddleware(
- RequestDelegate next,
- IRequestExecutorProvider executorResolver,
- IRequestExecutorEvents executorEvents,
- IHttpResponseFormatter responseFormatter,
- IServerDiagnosticEvents diagnosticEvents,
- string schemaName)
- : base(next, executorResolver, executorEvents, responseFormatter, schemaName)
- {
- _diagnosticEvents = diagnosticEvents ??
- throw new ArgumentNullException(nameof(diagnosticEvents));
- }
-
- public Task InvokeAsync(HttpContext context)
- {
- return context.WebSockets.IsWebSocketRequest
- ? HandleWebSocketSessionAsync(context)
- : NextAsync(context);
- }
-
- private async Task HandleWebSocketSessionAsync(HttpContext context)
- {
- if (!IsDefaultSchema)
- {
- context.Items[WellKnownContextData.SchemaName] = SchemaName;
- }
-
- using (_diagnosticEvents.WebSocketSession(context))
- {
- try
- {
- var executor = await GetExecutorAsync(context.RequestAborted);
- var interceptor = executor.GetRequiredService();
- context.Items[WellKnownContextData.RequestExecutor] = executor;
- await WebSocketSession.AcceptAsync(context, executor, interceptor);
- }
- catch (Exception ex)
- {
- _diagnosticEvents.WebSocketSessionError(context, ex);
- }
- }
- }
-}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/HotChocolate.Transport.Abstractions.csproj b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/HotChocolate.Transport.Abstractions.csproj
index 0b041dae748..57b130096c8 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/HotChocolate.Transport.Abstractions.csproj
+++ b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/HotChocolate.Transport.Abstractions.csproj
@@ -19,6 +19,7 @@
+
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationResult.cs b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationResult.cs
index b26239e200f..4df2cd0e068 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationResult.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationResult.cs
@@ -1,4 +1,5 @@
using System.Text.Json;
+using HotChocolate.Buffers;
using static HotChocolate.Transport.Properties.TransportAbstractionResources;
using static HotChocolate.Transport.Serialization.Utf8GraphQLResultProperties;
@@ -16,8 +17,7 @@ public sealed class OperationResult : IDisposable
/// specified JSON document and optional data, errors, and extensions.
///
///
- /// The memory owner of the json elements.
- /// operation.
+ /// The memory owner of the JSON elements.
///
///
/// A object representing the data returned by the operation.
@@ -86,6 +86,21 @@ public OperationResult(
public void Dispose()
=> _memoryOwner?.Dispose();
+ public static OperationResult Parse(JsonDocumentOwner documentOwner)
+ {
+ ArgumentNullException.ThrowIfNull(documentOwner);
+
+ var root = documentOwner.Document.RootElement;
+
+ return new OperationResult(
+ documentOwner,
+ root.TryGetProperty(DataProp, out var data) ? data : default,
+ root.TryGetProperty(ErrorsProp, out var errors) ? errors : default,
+ root.TryGetProperty(ExtensionsProp, out var extensions) ? extensions : default,
+ root.TryGetProperty(RequestIndexProp, out var requestIndex) ? requestIndex.GetInt32() : null,
+ root.TryGetProperty(VariableIndexProp, out var variableIndex) ? variableIndex.GetInt32() : null);
+ }
+
public static OperationResult Parse(JsonDocument document)
{
ArgumentNullException.ThrowIfNull(document);
@@ -110,11 +125,39 @@ public static OperationResult Parse(ReadOnlySpan span)
nameof(span));
}
- var reader = new Utf8JsonReader(span, true, default);
- var root = JsonElement.ParseValue(ref reader);
+ var buffer = new PooledArrayWriter();
+ var bufferSpan = buffer.GetSpan(span.Length);
+ span.CopyTo(bufferSpan);
+ buffer.Advance(span.Length);
+
+ var document = JsonDocument.Parse(buffer.WrittenMemory);
+ var root = document.RootElement;
+ var documentOwner = new JsonDocumentOwner(document, buffer);
+
+ return new OperationResult(
+ documentOwner,
+ root.TryGetProperty(DataProp, out var data) ? data : default,
+ root.TryGetProperty(ErrorsProp, out var errors) ? errors : default,
+ root.TryGetProperty(ExtensionsProp, out var extensions) ? extensions : default,
+ root.TryGetProperty(RequestIndexProp, out var requestIndex) ? requestIndex.GetInt32() : null,
+ root.TryGetProperty(VariableIndexProp, out var variableIndex) ? variableIndex.GetInt32() : null);
+ }
+
+ public static OperationResult Parse(PooledArrayWriter buffer)
+ {
+ if (buffer.WrittenSpan.Length == 0)
+ {
+ throw new ArgumentException(
+ OperationResult_Parse_JsonDataIsEmpty,
+ nameof(buffer));
+ }
+
+ var document = JsonDocument.Parse(buffer.WrittenMemory);
+ var root = document.RootElement;
+ var documentOwner = new JsonDocumentOwner(document, buffer);
return new OperationResult(
- null,
+ documentOwner,
root.TryGetProperty(DataProp, out var data) ? data : default,
root.TryGetProperty(ErrorsProp, out var errors) ? errors : default,
root.TryGetProperty(ExtensionsProp, out var extensions) ? extensions : default,
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Formatters/ConcurrentStreamWriter.cs b/src/HotChocolate/AspNetCore/src/Transport.Formatters/ConcurrentStreamWriter.cs
index 0e149e039a7..fc1ce4ff2e3 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Formatters/ConcurrentStreamWriter.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Formatters/ConcurrentStreamWriter.cs
@@ -99,7 +99,7 @@ private async Task ProcessBacklogAsync(CancellationToken ct)
try
{
currentBuffer = message;
- await _stream.WriteAsync(message.GetWrittenMemory(), ct).ConfigureAwait(false);
+ await _stream.WriteAsync(message.WrittenMemory, ct).ConfigureAwait(false);
await _stream.FlushAsync(ct).ConfigureAwait(false);
}
finally
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Formatters/EventStreamResultFormatter.cs b/src/HotChocolate/AspNetCore/src/Transport.Formatters/EventStreamResultFormatter.cs
index ffb1021651b..1a9ce6a4c0b 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Formatters/EventStreamResultFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Formatters/EventStreamResultFormatter.cs
@@ -66,7 +66,7 @@ private async ValueTask FormatOperationResultAsync(
if (!ct.IsCancellationRequested)
{
- await outputStream.WriteAsync(buffer.GetWrittenMemory(), ct).ConfigureAwait(false);
+ await outputStream.WriteAsync(buffer.WrittenMemory, ct).ConfigureAwait(false);
await outputStream.FlushAsync(ct).ConfigureAwait(false);
}
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonLinesResultFormatter.cs b/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonLinesResultFormatter.cs
index 8399e65b769..b02f615843c 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonLinesResultFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonLinesResultFormatter.cs
@@ -46,7 +46,7 @@ private async ValueTask FormatOperationResultAsync(
if (!ct.IsCancellationRequested)
{
- await outputStream.WriteAsync(buffer.GetWrittenMemory(), ct).ConfigureAwait(false);
+ await outputStream.WriteAsync(buffer.WrittenMemory, ct).ConfigureAwait(false);
await outputStream.FlushAsync(ct).ConfigureAwait(false);
}
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonResultFormatter.cs b/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonResultFormatter.cs
index 9f771377d72..ad01e86e8a3 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonResultFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Formatters/JsonResultFormatter.cs
@@ -146,6 +146,14 @@ public void FormatErrors(IReadOnlyList errors, Utf8JsonWriter writer)
writer.WriteEndArray();
}
+ public void FormatDictionary(IReadOnlyDictionary dictionary, Utf8JsonWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(dictionary);
+ ArgumentNullException.ThrowIfNull(writer);
+
+ WriteDictionary(writer, dictionary, _serializerOptions, _nullIgnoreCondition);
+ }
+
public void Format(IOperationResult result, IBufferWriter writer)
{
ArgumentNullException.ThrowIfNull(result);
@@ -181,7 +189,7 @@ private async ValueTask FormatInternalAsync(
FormatInternal(result, buffer);
await outputStream
- .WriteAsync(buffer.GetWrittenMemory(), cancellationToken)
+ .WriteAsync(buffer.WrittenMemory, cancellationToken)
.ConfigureAwait(false);
await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
@@ -223,7 +231,7 @@ private async ValueTask FormatInternalAsync(
}
await outputStream
- .WriteAsync(buffer.GetWrittenMemory(), cancellationToken)
+ .WriteAsync(buffer.WrittenMemory, cancellationToken)
.ConfigureAwait(false);
await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
@@ -251,7 +259,7 @@ private async ValueTask FormatInternalAsync(
}
await outputStream
- .WriteAsync(buffer.GetWrittenMemory(), cancellationToken)
+ .WriteAsync(buffer.WrittenMemory, cancellationToken)
.ConfigureAwait(false);
await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Formatters/MultiPartResultFormatter.cs b/src/HotChocolate/AspNetCore/src/Transport.Formatters/MultiPartResultFormatter.cs
index 27c2f63ab1b..6818239b5f3 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Formatters/MultiPartResultFormatter.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Formatters/MultiPartResultFormatter.cs
@@ -103,7 +103,7 @@ private async ValueTask FormatOperationResultAsync(
// Last we write the end of the part.
MessageHelper.WriteEnd(buffer);
- await outputStream.WriteAsync(buffer.GetInternalBuffer(), 0, buffer.Length, ct).ConfigureAwait(false);
+ await outputStream.WriteAsync(buffer.WrittenMemory, ct).ConfigureAwait(false);
await outputStream.FlushAsync(ct).ConfigureAwait(false);
}
@@ -123,7 +123,7 @@ private async ValueTask FormatResultBatchAsync(
MessageHelper.WriteResultHeader(buffer);
MessageHelper.WritePayload(buffer, operationResult, _payloadFormatter);
MessageHelper.WriteEnd(buffer);
- await outputStream.WriteAsync(buffer.GetInternalBuffer(), 0, buffer.Length, ct).ConfigureAwait(false);
+ await outputStream.WriteAsync(buffer.WrittenMemory, ct).ConfigureAwait(false);
await outputStream.FlushAsync(ct).ConfigureAwait(false);
break;
@@ -172,7 +172,7 @@ private async ValueTask FormatResponseStreamAsync(
}
// Now we can write the part to the output stream and flush this chunk.
- await outputStream.WriteAsync(buffer.GetInternalBuffer(), 0, buffer.Length, ct).ConfigureAwait(false);
+ await outputStream.WriteAsync(buffer.WrittenMemory, ct).ConfigureAwait(false);
await outputStream.FlushAsync(ct).ConfigureAwait(false);
}
finally
@@ -186,7 +186,7 @@ private async ValueTask FormatResponseStreamAsync(
// After all parts have been written, we need to write the final boundary.
buffer.Reset();
MessageHelper.WriteEnd(buffer);
- await outputStream.WriteAsync(buffer.GetInternalBuffer(), 0, buffer.Length, ct).ConfigureAwait(false);
+ await outputStream.WriteAsync(buffer.WrittenMemory, ct).ConfigureAwait(false);
await outputStream.FlushAsync(ct).ConfigureAwait(false);
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs b/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs
index 1ecb5a17fa6..77196d02e2c 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs
@@ -181,7 +181,8 @@ private static ByteArrayContent CreatePostContent(
request.Body.WriteTo(jsonWriter);
jsonWriter.Flush();
- var content = new ByteArrayContent(arrayWriter.GetInternalBuffer(), 0, arrayWriter.Length);
+ var internalBuffer = PooledArrayWriterMarshal.GetUnderlyingBuffer(arrayWriter);
+ var content = new ByteArrayContent(internalBuffer, 0, arrayWriter.Length);
content.Headers.ContentType = new MediaTypeHeaderValue(ContentType.Json, "utf-8");
return content;
}
@@ -200,7 +201,7 @@ private static HttpContent CreateMultipartContent(
var start = arrayWriter.Length;
WriteOperationJson(arrayWriter, request);
- var buffer = arrayWriter.GetInternalBuffer();
+ var buffer = PooledArrayWriterMarshal.GetUnderlyingBuffer(arrayWriter);
var form = new MultipartFormDataContent();
@@ -320,7 +321,7 @@ private static string FormatDocumentAsJson(PooledArrayWriter arrayWriter, object
Utf8JsonWriterHelper.WriteFieldValue(jsonWriter, obj);
jsonWriter.Flush();
- return Encoding.UTF8.GetString(arrayWriter.GetWrittenSpan());
+ return Encoding.UTF8.GetString(arrayWriter.WrittenSpan);
}
protected override void Dispose(bool disposing)
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpClient.cs b/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpClient.cs
index 6e345b21ca4..cc60b55d70d 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpClient.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpClient.cs
@@ -49,6 +49,6 @@ protected virtual void Dispose(bool disposing) { }
///
/// Returns the new <see cref="GraphQLHttpClient"/> instance.
///
- public static GraphQLHttpClient Create(HttpClient httpClient, bool disposeHttpClient)
+ public static GraphQLHttpClient Create(HttpClient httpClient, bool disposeHttpClient = true)
=> new DefaultGraphQLHttpClient(httpClient, disposeHttpClient);
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpEventStreamEnumerable.cs b/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpEventStreamEnumerable.cs
index 97dc8988666..5d08e84c54e 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpEventStreamEnumerable.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Http/GraphQLHttpEventStreamEnumerable.cs
@@ -1,6 +1,5 @@
using System.Buffers;
using System.IO.Pipelines;
-using System.Text;
using HotChocolate.Buffers;
using HotChocolate.Utilities;
@@ -94,7 +93,7 @@ public async ValueTask MoveNextAsync()
if (IsMessageComplete(_messageBuffer))
{
- var readState = TryReadResult(_messageBuffer.GetWrittenSpan(), out var operationResult);
+ var readState = TryReadResult(_messageBuffer.WrittenSpan, out var operationResult);
if (readState is ReadState.Message)
{
@@ -149,7 +148,7 @@ private static void WriteLineToMessage(PooledArrayWriter message, ReadOnlySequen
private static bool IsMessageComplete(PooledArrayWriter message)
{
- var span = message.GetWrittenSpan();
+ var span = message.WrittenSpan;
return span.Length >= 2 && span[^1] == (byte)'\n' && span[^2] == (byte)'\n';
}
@@ -200,48 +199,57 @@ private static OperationResult ParseData(ref ReadOnlySpan span)
SkipWhitespaces(ref span);
- using var payload = new PooledArrayWriter();
+ var payload = new PooledArrayWriter();
- while (true)
+ try
{
- // read one logical line up to LF or end
- var lineBreak = span.IndexOf((byte)'\n');
- ReadOnlySpan line;
- if (lineBreak == -1)
- {
- line = span;
- span = default;
- }
- else
+ while (true)
{
- line = span[..lineBreak];
- span = span[(lineBreak + 1)..];
- }
+ // read one logical line up to LF or end
+ var lineBreak = span.IndexOf((byte)'\n');
+ ReadOnlySpan line;
+ if (lineBreak == -1)
+ {
+ line = span;
+ span = default;
+ }
+ else
+ {
+ line = span[..lineBreak];
+ span = span[(lineBreak + 1)..];
+ }
- // Remove optional leading space
- SkipWhitespaces(ref line);
+ // Remove optional leading space
+ SkipWhitespaces(ref line);
- // append to buffer (insert LF between lines)
- if (payload.Length > 0)
- {
- payload.GetSpan(1)[0] = (byte)'\n';
- payload.Advance(1);
- }
- line.CopyTo(payload.GetSpan(line.Length));
- payload.Advance(line.Length);
+ // append to buffer (insert LF between lines)
+ if (payload.Length > 0)
+ {
+ payload.GetSpan(1)[0] = (byte)'\n';
+ payload.Advance(1);
+ }
- // if the next part does not start with another data: line we are done
- if (span.Length < 5 || !span.StartsWith(Data))
- {
- break;
+ line.CopyTo(payload.GetSpan(line.Length));
+ payload.Advance(line.Length);
+
+ // if the next part does not start with another data: line, we are done
+ if (span.Length < 5 || !span.StartsWith(Data))
+ {
+ break;
+ }
+
+ // consume the next "data:" token and the following whitespace
+ span = span[5..];
+ SkipWhitespaces(ref span);
}
- // consume next "data:" token & following whitespace
- span = span[5..];
- SkipWhitespaces(ref span);
+ return OperationResult.Parse(payload);
+ }
+ catch
+ {
+ payload.Dispose();
+ throw;
}
-
- return OperationResult.Parse(payload.GetWrittenSpan());
}
private static async Task ReadFromTransportAsync(Stream stream, PipeWriter writer, CancellationToken ct)
@@ -286,8 +294,6 @@ private static async Task ReadFromTransportAsync(Stream stream, PipeWriter write
}
}
- var debug = Encoding.UTF8.GetString(temp.GetWrittenSpan());
-
await writer.CompleteAsync().ConfigureAwait(false);
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/MessageHelper.cs b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/MessageHelper.cs
index 3b96bd5a6e9..7c74a40edcf 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/MessageHelper.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/MessageHelper.cs
@@ -34,7 +34,7 @@ public static async ValueTask SendConnectionInitMessage(
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(ct).ConfigureAwait(false);
- await socket.SendAsync(arrayWriter.GetWrittenMemory(), Text, true, ct).ConfigureAwait(false);
+ await socket.SendAsync(arrayWriter.WrittenMemory, Text, true, ct).ConfigureAwait(false);
}
public static async ValueTask SendSubscribeMessageAsync(
@@ -56,7 +56,7 @@ public static async ValueTask SendSubscribeMessageAsync(
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(ct).ConfigureAwait(false);
- await socket.SendAsync(arrayWriter.GetWrittenMemory(), Text, true, ct).ConfigureAwait(false);
+ await socket.SendAsync(arrayWriter.WrittenMemory, Text, true, ct).ConfigureAwait(false);
}
public static async ValueTask SendCompleteMessageAsync(
@@ -72,7 +72,7 @@ public static async ValueTask SendCompleteMessageAsync(
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(ct).ConfigureAwait(false);
- await socket.SendAsync(arrayWriter.GetWrittenMemory(), Text, true, ct).ConfigureAwait(false);
+ await socket.SendAsync(arrayWriter.WrittenMemory, Text, true, ct).ConfigureAwait(false);
}
public static async ValueTask SendPongMessageAsync(
@@ -86,6 +86,6 @@ public static async ValueTask SendPongMessageAsync(
jsonWriter.WriteEndObject();
await jsonWriter.FlushAsync(ct).ConfigureAwait(false);
- await socket.SendAsync(arrayWriter.GetWrittenMemory(), Text, true, ct).ConfigureAwait(false);
+ await socket.SendAsync(arrayWriter.WrittenMemory, Text, true, ct).ConfigureAwait(false);
}
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/ErrorMessage.cs b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/ErrorMessage.cs
index 28ee8672095..75425618d1e 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/ErrorMessage.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/ErrorMessage.cs
@@ -26,17 +26,21 @@ public static ErrorMessage From(ReadOnlySequence message)
var arrayWriter = new PooledArrayWriter();
arrayWriter.Write(message);
- var document = JsonDocument.Parse(arrayWriter.GetWrittenMemory());
+ var document = JsonDocument.Parse(arrayWriter.WrittenMemory);
+
var root = document.RootElement;
var id = root.GetProperty(Utf8MessageProperties.IdProp).GetString();
if (id is null)
{
+ arrayWriter.Dispose();
+ document.Dispose();
throw ThrowHelper.MessageHasNoId();
}
+ var documentOwner = new JsonDocumentOwner(document, arrayWriter);
var payload = root.GetProperty(Utf8MessageProperties.PayloadProp);
- var result = new OperationResult(arrayWriter, errors: payload);
+ var result = new OperationResult(documentOwner, errors: payload);
return new ErrorMessage(id, result);
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/NextMessage.cs b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/NextMessage.cs
index 3ba53bc0669..18032cd3ada 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/NextMessage.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/Protocols/GraphQLOverWebSocket/Messages/NextMessage.cs
@@ -20,20 +20,24 @@ private NextMessage(string id, OperationResult payload)
public static NextMessage From(ReadOnlySequence message)
{
- // The ArrayWriter is used to copy the message because otherwise the buffer is reused and
- // causes problems. The ArrayWriter is passed to the OperationResult where it's stored as
- // the memory owner and disposed when the OperationResult is disposed.
+ // The ArrayWriter is used to copy the message because otherwise, the buffer is reused and
+ // causes problems. We bundle the arrayWriter and the document into a JsonDocumentOwner,
+ // which will be passed on with the operation result as the memory owner.
+ // The JsonDocumentOwner will be disposed when the OperationResult is disposed and in turn
+ // returns the memory to the pool and disposes the JsonDocument.
var arrayWriter = new PooledArrayWriter();
arrayWriter.Write(message);
- var document = JsonDocument.Parse(arrayWriter.GetWrittenMemory());
+ var document = JsonDocument.Parse(arrayWriter.WrittenMemory);
+ var documentOwner = new JsonDocumentOwner(document, arrayWriter);
var root = document.RootElement;
- var id = root.GetProperty(Utf8MessageProperties.IdProp).GetString()!;
+ var id = root.GetProperty(Utf8MessageProperties.IdProp).GetString()!;
var payload = root.GetProperty(Utf8MessageProperties.PayloadProp);
+
var result = new OperationResult(
- arrayWriter,
+ documentOwner,
TryGetProperty(payload, Utf8MessageProperties.DataProp),
TryGetProperty(payload, Utf8MessageProperties.ErrorsProp),
TryGetProperty(payload, Utf8MessageProperties.ExtensionsProp));
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/SocketClient.cs b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/SocketClient.cs
index 9db48d4245b..1f2369f306b 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/SocketClient.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Sockets.Client/SocketClient.cs
@@ -29,16 +29,19 @@ private SocketClient(WebSocket socket, IProtocolHandler protocol)
_protocol = protocol;
_context = new SocketClientContext(socket);
_pipeline = new MessagePipeline(this, new MessageHandler(_context, protocol));
- _pipeline.Completed += (_, _) =>
- {
- if (_context.Socket.CloseStatus is not null)
+ _pipeline.OnCompleted(
+ static context =>
{
- _context.Messages.OnError(new SocketClosedException(
- _context.Socket.CloseStatusDescription ?? "Socket was closed.",
- _context.Socket.CloseStatus.Value));
- }
- _context.Messages.OnCompleted();
- };
+ if (context.Socket.CloseStatus is not null)
+ {
+ context.Messages.OnError(
+ new SocketClosedException(
+ context.Socket.CloseStatusDescription ?? "Socket was closed.",
+ context.Socket.CloseStatus.Value));
+ }
+ context.Messages.OnCompleted();
+ },
+ _context);
_ct = _cts.Token;
}
diff --git a/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs b/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs
index 31706a11e60..076d980e54e 100644
--- a/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs
+++ b/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs
@@ -1,3 +1,4 @@
+using System.Collections.Immutable;
using System.IO.Pipelines;
namespace HotChocolate.Transport.Sockets;
@@ -9,8 +10,7 @@ public sealed class MessagePipeline
{
private readonly MessageReceiver _messageReceiver;
private readonly MessageProcessor _messageProcessor;
-
- public EventHandler? Completed;
+ private ImmutableArray<(Action
///
/// Accessing the underlying buffer directly is not recommended.
- /// If possible use or .
+ /// If possible use or .
///
- public byte[] GetInternalBuffer() => _buffer;
+ internal byte[] GetInternalBuffer() => _buffer;
///
/// Gets the part of the buffer that has been written to.
@@ -53,16 +53,19 @@ public PooledArrayWriter()
///
/// A of the written portion of the buffer.
///
- public ReadOnlyMemory GetWrittenMemory()
+ public ReadOnlyMemory WrittenMemory
=> _buffer.AsMemory()[.._start];
+ Memory IMemoryOwner.Memory
+ => _buffer.AsMemory(0, _start);
+
///
/// Gets the part of the buffer that has been written to.
///
///
/// A of the written portion of the buffer.
///
- public ReadOnlySpan GetWrittenSpan()
+ public ReadOnlySpan WrittenSpan
=> MemoryMarshal.CreateSpan(ref _buffer[0], _start);
///
diff --git a/src/HotChocolate/Utilities/src/Utilities.Buffers/PooledArrayWriterMarshal.cs b/src/HotChocolate/Utilities/src/Utilities.Buffers/PooledArrayWriterMarshal.cs
new file mode 100644
index 00000000000..c6b6da07ecd
--- /dev/null
+++ b/src/HotChocolate/Utilities/src/Utilities.Buffers/PooledArrayWriterMarshal.cs
@@ -0,0 +1,22 @@
+namespace HotChocolate.Buffers;
+
+///
+/// A helper class to marshal the underlying buffer of a .
+///
+public static class PooledArrayWriterMarshal
+{
+ ///
+ /// Gets the underlying buffer of a .
+ ///
+ ///
+ /// The to get the underlying buffer from.
+ ///
+ ///
+ /// The underlying buffer of the .
+ ///
+ public static byte[] GetUnderlyingBuffer(PooledArrayWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+ return writer.GetInternalBuffer();
+ }
+}
diff --git a/src/HotChocolate/Utilities/src/Utilities/BufferHelper.cs b/src/HotChocolate/Utilities/src/Utilities/BufferHelper.cs
deleted file mode 100644
index 90e889b701f..00000000000
--- a/src/HotChocolate/Utilities/src/Utilities/BufferHelper.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System.Buffers;
-
-namespace HotChocolate.Utilities;
-
-public static class BufferHelper
-{
- public static Task ReadAsync(
- Stream stream,
- Func deserialize,
- CancellationToken cancellationToken)
- => ReadAsync(stream, deserialize, static (b, l, d) => d(b, l), cancellationToken);
-
- public static Task ReadAsync(
- Stream stream,
- TState state,
- Func deserialize,
- CancellationToken cancellationToken)
- {
- ArgumentNullException.ThrowIfNull(stream);
- ArgumentNullException.ThrowIfNull(deserialize);
-
- return ReadAsync(stream, state, int.MaxValue, deserialize, () => { }, cancellationToken);
- }
-
- public static async Task ReadAsync(
- Stream stream,
- TState state,
- int maxAllowedSize,
- Func deserialize,
- Action throwMaxAllowedError,
- CancellationToken cancellationToken)
- {
- var buffer = ArrayPool.Shared.Rent(1024);
- var bytesBuffered = 0;
-
- try
- {
- while (true)
- {
- var bytesRemaining = buffer.Length - bytesBuffered;
-
- if (bytesRemaining == 0)
- {
- var next = ArrayPool.Shared.Rent(buffer.Length * 2);
- Buffer.BlockCopy(buffer, 0, next, 0, buffer.Length);
- ArrayPool.Shared.Return(buffer);
- buffer = next;
- bytesRemaining = buffer.Length - bytesBuffered;
- }
-
- var bytesRead = await stream.ReadAsync(
- buffer,
- bytesBuffered,
- bytesRemaining,
- cancellationToken)
- .ConfigureAwait(false);
- if (bytesRead == 0)
- {
- break;
- }
-
- bytesBuffered += bytesRead;
-
- if (bytesBuffered > maxAllowedSize)
- {
- throwMaxAllowedError();
- }
- }
-
- return deserialize(buffer, bytesBuffered, state);
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
- }
-}
diff --git a/src/HotChocolate/Utilities/test/Utilities.Tests/PooledArrayWriterTests.cs b/src/HotChocolate/Utilities/test/Utilities.Tests/PooledArrayWriterTests.cs
index e49f1feafb0..8d8d7c88424 100644
--- a/src/HotChocolate/Utilities/test/Utilities.Tests/PooledArrayWriterTests.cs
+++ b/src/HotChocolate/Utilities/test/Utilities.Tests/PooledArrayWriterTests.cs
@@ -11,7 +11,7 @@ public void Constructor_ShouldInitializeProperly()
using var writer = new PooledArrayWriter();
// Assert
- Assert.NotNull(writer.GetInternalBuffer());
+ Assert.NotNull(PooledArrayWriterMarshal.GetUnderlyingBuffer(writer));
Assert.Equal(0, writer.Length);
}
@@ -22,7 +22,7 @@ public void GetWrittenMemory_ShouldReturnReadOnlyMemory()
using var writer = new PooledArrayWriter();
// Act
- var memory = writer.GetWrittenMemory();
+ var memory = writer.WrittenMemory;
// Assert
Assert.Equal(0, memory.Length);
@@ -35,7 +35,7 @@ public void GetWrittenSpan_ShouldReturnReadOnlySpan()
using var writer = new PooledArrayWriter();
// Act
- var span = writer.GetWrittenSpan();
+ var span = writer.WrittenSpan;
// Assert
Assert.Equal(0, span.Length);
@@ -125,7 +125,7 @@ public void Advance_ShouldThrowWhenCountGreaterThanCapacity()
// Act & Assert
Assert.Throws(
- () => writer.Advance(writer.GetInternalBuffer().Length + 1));
+ () => writer.Advance(PooledArrayWriterMarshal.GetUnderlyingBuffer(writer).Length + 1));
}
[Fact]
@@ -184,7 +184,7 @@ public void WriteBytesToSpan_ShouldWriteCorrectly()
// Assert
Assert.Equal(4, writer.Length);
- var writtenSpan = writer.GetWrittenSpan();
+ var writtenSpan = writer.WrittenSpan;
Assert.True(testData.SequenceEqual(writtenSpan.ToArray()));
}
@@ -202,7 +202,7 @@ public void WriteBytesToMemory_ShouldWriteCorrectly()
// Assert
Assert.Equal(4, writer.Length);
- var writtenMemory = writer.GetWrittenMemory();
+ var writtenMemory = writer.WrittenMemory;
Assert.True(testData.SequenceEqual(writtenMemory.ToArray()));
}
@@ -228,7 +228,7 @@ public void WriteBytesExceedingInitialBufferSize_ShouldExpandAndWriteCorrectly()
// Assert
Assert.Equal(1024, writer.Length);
- var writtenSpan = writer.GetWrittenSpan();
+ var writtenSpan = writer.WrittenSpan;
Assert.True(testData.SequenceEqual(writtenSpan.ToArray()));
}
@@ -239,8 +239,8 @@ public void ShouldAllocateSufficientMemory()
using var writer = new PooledArrayWriter();
// Act
- // NB: ask for 0x3000 bytes because the initial 512 bytes buffer size is added
- // to request when doubling is insufficient and ArrayPool sizes are powers of 2
+ // NB: ask for 0x3000 bytes because the initial 512-byte buffer size is added
+ // to request when doubling is not enough and ArrayPool sizes are powers of 2
writer.GetSpan(0x3000);
writer.Advance(0x2000);
writer.GetSpan(0x7000);
diff --git a/src/StrawberryShake/Client/src/Transport.Http/ResponseEnumerable.cs b/src/StrawberryShake/Client/src/Transport.Http/ResponseEnumerable.cs
index ee687ff9596..4bc9017a87e 100644
--- a/src/StrawberryShake/Client/src/Transport.Http/ResponseEnumerable.cs
+++ b/src/StrawberryShake/Client/src/Transport.Http/ResponseEnumerable.cs
@@ -112,7 +112,7 @@ public async IAsyncEnumerator> GetAsyncEnumerator(
writer.Flush();
- return JsonDocument.Parse(buffer.GetWrittenMemory());
+ return JsonDocument.Parse(buffer.WrittenMemory);
}
private static void WriteProperty(Utf8JsonWriter writer, string propertyName, JsonElement value)
diff --git a/src/StrawberryShake/Client/test/Transport.InMemory.Tests/InMemoryClientTests.cs b/src/StrawberryShake/Client/test/Transport.InMemory.Tests/InMemoryClientTests.cs
index a7d69bb931d..2cf817fd23a 100644
--- a/src/StrawberryShake/Client/test/Transport.InMemory.Tests/InMemoryClientTests.cs
+++ b/src/StrawberryShake/Client/test/Transport.InMemory.Tests/InMemoryClientTests.cs
@@ -130,6 +130,8 @@ private sealed class StubExecutor : IRequestExecutor
public ulong Version { get; }
+ public IFeatureCollection Features { get; } = new FeatureCollection();
+
public Task ExecuteAsync(
IOperationRequest request,
CancellationToken cancellationToken = default)
diff --git a/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/IntegrationTests.cs b/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/IntegrationTests.cs
index 4d21bb678a9..a0adf2ca7c3 100644
--- a/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/IntegrationTests.cs
+++ b/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/IntegrationTests.cs
@@ -295,7 +295,7 @@ public override ValueTask OnConnectAsync(
IOperationMessagePayload connectionInitMessage,
CancellationToken cancellationToken = default)
{
- InitializeConnectionMessage = connectionInitMessage.As>();
+ InitializeConnectionMessage = connectionInitMessage.Payload?.Deserialize>();
return base.OnConnectAsync(session, connectionInitMessage, cancellationToken);
}
diff --git a/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/TestHelper/TestServerHelper.cs b/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/TestHelper/TestServerHelper.cs
index 5c1dcaedb93..143d9d304d1 100644
--- a/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/TestHelper/TestServerHelper.cs
+++ b/src/StrawberryShake/Client/test/Transport.WebSocket.Tests/TestHelper/TestServerHelper.cs
@@ -7,7 +7,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using static HotChocolate.WellKnownContextData;
namespace StrawberryShake.Transport.WebSockets;
@@ -60,7 +59,7 @@ value is HttpContext httpContext &&
context.Result = result =
OperationResultBuilder
.FromResult(result)
- .SetContextData(HttpStatusCode, 403)
+ .SetContextData(ExecutionContextData.HttpStatusCode, 403)
.Build();
}