Skip to content

Commit

Permalink
Fix graphql api with parameters (#15529)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyzx86 authored Mar 21, 2024
1 parent 7326e8c commit 5d67de4
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Net;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Execution;
Expand All @@ -28,6 +27,7 @@ namespace OrchardCore.Apis.GraphQL
public class GraphQLMiddleware : IMiddleware
{
private readonly GraphQLSettings _settings;
private readonly IGraphQLTextSerializer _graphQLTextSerializer;
private readonly IGraphQLSerializer _serializer;
private readonly IDocumentExecuter _executer;
internal static readonly Encoding _utf8Encoding = new UTF8Encoding(false);
Expand All @@ -37,11 +37,13 @@ public class GraphQLMiddleware : IMiddleware
public GraphQLMiddleware(
IOptions<GraphQLSettings> settingsOption,
IDocumentExecuter executer,
IGraphQLSerializer serializer)
IGraphQLSerializer serializer,
IGraphQLTextSerializer graphQLTextSerializer)
{
_settings = settingsOption.Value;
_executer = executer;
_serializer = serializer;
_graphQLTextSerializer = graphQLTextSerializer;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Expand Down Expand Up @@ -89,19 +91,17 @@ private async Task ExecuteAsync(HttpContext context)

if (mediaType.IsSubsetOf(_jsonMediaType) || mediaType.IsSubsetOf(_graphQlMediaType))
{

using var sr = new StreamReader(context.Request.Body);
if (mediaType.IsSubsetOf(_graphQlMediaType))
{
using var sr = new StreamReader(context.Request.Body);

request = new GraphQLNamedQueryRequest
{
Query = await sr.ReadToEndAsync()
};
}
else
{
request = await JsonSerializer.DeserializeAsync<GraphQLNamedQueryRequest>(context.Request.Body, JOptions.CamelCase);
request = _graphQLTextSerializer.Deserialize<GraphQLNamedQueryRequest>(await sr.ReadToEndAsync());
}
}
else
Expand Down Expand Up @@ -171,7 +171,7 @@ private async Task ExecuteAsync(HttpContext context)
await _serializer.WriteAsync(context.Response.Body, result);
}

private static GraphQLNamedQueryRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false)
private GraphQLNamedQueryRequest CreateRequestFromQueryString(HttpContext context, bool validateQueryKey = false)
{
if (!context.Request.Query.ContainsKey("query"))
{
Expand All @@ -190,7 +190,7 @@ private static GraphQLNamedQueryRequest CreateRequestFromQueryString(HttpContext

if (context.Request.Query.ContainsKey("variables"))
{
request.Variables = JsonSerializer.Deserialize<Inputs>(context.Request.Query["variables"], JOptions.CamelCase);
request.Variables = _graphQLTextSerializer.Deserialize<Inputs>(context.Request.Query["variables"]);
}

if (context.Request.Query.ContainsKey("operationName"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@

using GraphQL.Transport;
using OrchardCore.Apis.GraphQL.Queries;

namespace OrchardCore.Apis.GraphQL
{
public class GraphQLNamedQueryRequest : GraphQLRequest
{
/// <summary>
/// Used to store some graphql query on the server, and then the client only needs to submit the name of that query to reduce the size of the network request
/// <see cref="INamedQueryProvider"/>
/// </summary>
public string NamedQuery { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using GraphQL;

namespace OrchardCore.Apis.GraphQL.Json;

public class GraphQLNamedQueryRequestJsonConverter : JsonConverter<GraphQLNamedQueryRequest>
{
public static readonly GraphQLNamedQueryRequestJsonConverter Instance = new();

/// <summary>
/// Name for the operation name parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _operationNameKey = "operationName";

/// <summary>
/// Name for the query parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _queryKey = "query";

/// <summary>
/// Name for the variables parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _variablesKey = "variables";

/// <summary>
/// Name for the extensions parameter.
/// See https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#request-parameters
/// </summary>
private const string _extensionsKey = "extensions";

/// <summary>
/// Name for the namedQuery parameter.
/// </summary>
private const string _namedQueryKey = "namedQuery";

public override void Write(Utf8JsonWriter writer, GraphQLNamedQueryRequest value, JsonSerializerOptions options)
{
writer.WriteStartObject();
if (value.Query != null)
{
writer.WritePropertyName(_queryKey);
writer.WriteStringValue(value.Query);
}

if (value.OperationName != null)
{
writer.WritePropertyName(_operationNameKey);
writer.WriteStringValue(value.OperationName);
}

if (value.Variables != null)
{
writer.WritePropertyName(_variablesKey);
JsonSerializer.Serialize(writer, value.Variables, options);
}

if (value.Extensions != null)
{
writer.WritePropertyName(_extensionsKey);
JsonSerializer.Serialize(writer, value.Extensions, options);
}

if (value.NamedQuery != null)
{
writer.WritePropertyName(_namedQueryKey);
JsonSerializer.Serialize(writer, value.NamedQuery, options);
}

writer.WriteEndObject();
}

public override GraphQLNamedQueryRequest Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

var request = new GraphQLNamedQueryRequest();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return request;
}

if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

var key = reader.GetString()!;

// Unexpected end of data.
if (!reader.Read())
throw new JsonException();

switch (key)
{
case _queryKey:
request.Query = reader.GetString()!;
break;
case _operationNameKey:
request.OperationName = reader.GetString()!;
break;
case _namedQueryKey:
request.NamedQuery = reader.GetString();
break;
case _variablesKey:
request.Variables = JsonSerializer.Deserialize<Inputs>(ref reader, options);
break;
case _extensionsKey:
request.Extensions = JsonSerializer.Deserialize<Inputs>(ref reader, options);
break;
default:
reader.Skip();
break;
}
}

// Unexpected end of data.
throw new JsonException();
}
}
14 changes: 13 additions & 1 deletion src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using OrchardCore.Apis.GraphQL.Json;
using OrchardCore.Apis.GraphQL.Services;
using OrchardCore.Apis.GraphQL.ValidationRules;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Extensions;
using OrchardCore.Json;
using OrchardCore.Json.Extensions;
using OrchardCore.Modules;
using OrchardCore.Navigation;
using OrchardCore.Security.Permissions;
Expand Down Expand Up @@ -46,7 +50,15 @@ public override void ConfigureServices(IServiceCollection services)
services.AddScoped<IPermissionProvider, Permissions>();
services.AddTransient<INavigationProvider, AdminMenu>();
services.AddSingleton<GraphQLMiddleware>();
services.AddGraphQL(builder => builder.AddSystemTextJson());

services.AddGraphQL(builder => builder.AddSystemTextJson((options, sp) =>
{
// Common types of converters are already configured in the assembly "GraphQL.SystemTextJson".
options.Converters.Add(GraphQLNamedQueryRequestJsonConverter.Instance);
var contentSerializerJsonOptions = sp.GetRequiredService<IOptions<ContentSerializerJsonOptions>>().Value;
options.Merge(contentSerializerJsonOptions.SerializerOptions);
}));

services.AddOptions<GraphQLSettings>().Configure<IShellConfiguration>((c, configuration) =>
{
Expand Down

0 comments on commit 5d67de4

Please sign in to comment.